* updated SNAND/SNFI driver brings support for MT7981 * add support for MediaTek NAND Memory bad Block Management (NMBM) (not used for any boards atm, but could be useful in future) * wire up NMBM support for MT7622, MT7629, MT7981 and MT7986 * replace some local patches with updated version from SDK * bring some legacy precompiler symbols which haven't been converted into Kconfig symbols in U-Boot 2022.07, remove when bumbping to U-Boot 2022.10: 100-28-include-configs-mt7986-h-from-SDK.patch Source: https://github.com/mtk-openwrt/u-boot Signed-off-by: Daniel Golle <daniel@makrotopia.org>
		
			
				
	
	
		
			4001 lines
		
	
	
		
			105 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			4001 lines
		
	
	
		
			105 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
From f7704275957852cd4c4632d6da126979ef24b83a Mon Sep 17 00:00:00 2001
 | 
						|
From: Weijie Gao <weijie.gao@mediatek.com>
 | 
						|
Date: Tue, 2 Mar 2021 16:58:01 +0800
 | 
						|
Subject: [PATCH 36/71] drivers: mtd: add support for MediaTek SPI-NAND flash
 | 
						|
 controller
 | 
						|
 | 
						|
Add mtd driver for MediaTek SPI-NAND flash controller
 | 
						|
 | 
						|
This driver is written from scratch, and uses standard mtd framework, not
 | 
						|
the nand framework which only applies for raw parallel nand flashes so that
 | 
						|
this driver can have a smaller size in binary.
 | 
						|
 | 
						|
Signed-off-by: Weijie Gao <weijie.gao@mediatek.com>
 | 
						|
---
 | 
						|
 drivers/mtd/Kconfig                   |    2 +
 | 
						|
 drivers/mtd/Makefile                  |    2 +
 | 
						|
 drivers/mtd/mtk-snand/Kconfig         |   21 +
 | 
						|
 drivers/mtd/mtk-snand/Makefile        |   11 +
 | 
						|
 drivers/mtd/mtk-snand/mtk-snand-def.h |  271 ++++
 | 
						|
 drivers/mtd/mtk-snand/mtk-snand-ecc.c |  395 +++++
 | 
						|
 drivers/mtd/mtk-snand/mtk-snand-ids.c |  511 +++++++
 | 
						|
 drivers/mtd/mtk-snand/mtk-snand-mtd.c |  535 +++++++
 | 
						|
 drivers/mtd/mtk-snand/mtk-snand-os.c  |   39 +
 | 
						|
 drivers/mtd/mtk-snand/mtk-snand-os.h  |  120 ++
 | 
						|
 drivers/mtd/mtk-snand/mtk-snand.c     | 1933 +++++++++++++++++++++++++
 | 
						|
 drivers/mtd/mtk-snand/mtk-snand.h     |   77 +
 | 
						|
 12 files changed, 3917 insertions(+)
 | 
						|
 create mode 100644 drivers/mtd/mtk-snand/Kconfig
 | 
						|
 create mode 100644 drivers/mtd/mtk-snand/Makefile
 | 
						|
 create mode 100644 drivers/mtd/mtk-snand/mtk-snand-def.h
 | 
						|
 create mode 100644 drivers/mtd/mtk-snand/mtk-snand-ecc.c
 | 
						|
 create mode 100644 drivers/mtd/mtk-snand/mtk-snand-ids.c
 | 
						|
 create mode 100644 drivers/mtd/mtk-snand/mtk-snand-mtd.c
 | 
						|
 create mode 100644 drivers/mtd/mtk-snand/mtk-snand-os.c
 | 
						|
 create mode 100644 drivers/mtd/mtk-snand/mtk-snand-os.h
 | 
						|
 create mode 100644 drivers/mtd/mtk-snand/mtk-snand.c
 | 
						|
 create mode 100644 drivers/mtd/mtk-snand/mtk-snand.h
 | 
						|
 | 
						|
--- a/drivers/mtd/Kconfig
 | 
						|
+++ b/drivers/mtd/Kconfig
 | 
						|
@@ -158,6 +158,8 @@ config SYS_MAX_FLASH_BANKS_DETECT
 | 
						|
 	 to reduce the effective number of flash bank, between 0 and
 | 
						|
 	 CONFIG_SYS_MAX_FLASH_BANKS
 | 
						|
 
 | 
						|
+source "drivers/mtd/mtk-snand/Kconfig"
 | 
						|
+
 | 
						|
 source "drivers/mtd/nand/Kconfig"
 | 
						|
 
 | 
						|
 config SYS_NAND_MAX_CHIPS
 | 
						|
--- a/drivers/mtd/Makefile
 | 
						|
+++ b/drivers/mtd/Makefile
 | 
						|
@@ -39,3 +39,5 @@ obj-$(CONFIG_$(SPL_TPL_)SPI_FLASH_SUPPOR
 | 
						|
 obj-$(CONFIG_SPL_UBI) += ubispl/
 | 
						|
 
 | 
						|
 endif
 | 
						|
+
 | 
						|
+obj-$(CONFIG_MTK_SPI_NAND) += mtk-snand/
 | 
						|
--- /dev/null
 | 
						|
+++ b/drivers/mtd/mtk-snand/Kconfig
 | 
						|
@@ -0,0 +1,21 @@
 | 
						|
+#
 | 
						|
+# Copyright (C) 2020 MediaTek Inc. All rights reserved.
 | 
						|
+# Author: Weijie Gao <weijie.gao@mediatek.com>
 | 
						|
+#
 | 
						|
+# SPDX-License-Identifier: GPL-2.0
 | 
						|
+#
 | 
						|
+
 | 
						|
+config MTK_SPI_NAND
 | 
						|
+	tristate "MediaTek SPI NAND flash controller driver"
 | 
						|
+	depends on !MTD_SPI_NAND
 | 
						|
+	help
 | 
						|
+	  This option enables access to SPI-NAND flashes through the
 | 
						|
+	  MediaTek SPI NAND Flash Controller
 | 
						|
+
 | 
						|
+config MTK_SPI_NAND_MTD
 | 
						|
+	tristate "MTD support for MediaTek SPI NAND flash controller"
 | 
						|
+	depends on DM_MTD
 | 
						|
+	depends on MTK_SPI_NAND
 | 
						|
+	help
 | 
						|
+	  This option enables access to SPI-NAND flashes through the
 | 
						|
+	  MTD interface of MediaTek SPI NAND Flash Controller
 | 
						|
--- /dev/null
 | 
						|
+++ b/drivers/mtd/mtk-snand/Makefile
 | 
						|
@@ -0,0 +1,11 @@
 | 
						|
+#
 | 
						|
+# Copyright (C) 2020 MediaTek Inc. All rights reserved.
 | 
						|
+# Author: Weijie Gao <weijie.gao@mediatek.com>
 | 
						|
+#
 | 
						|
+# SPDX-License-Identifier: GPL-2.0
 | 
						|
+#
 | 
						|
+
 | 
						|
+obj-y += mtk-snand.o mtk-snand-ecc.o mtk-snand-ids.o mtk-snand-os.o
 | 
						|
+obj-$(CONFIG_MTK_SPI_NAND_MTD) += mtk-snand-mtd.o
 | 
						|
+
 | 
						|
+ccflags-y += -DPRIVATE_MTK_SNAND_HEADER
 | 
						|
--- /dev/null
 | 
						|
+++ b/drivers/mtd/mtk-snand/mtk-snand-def.h
 | 
						|
@@ -0,0 +1,271 @@
 | 
						|
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
 | 
						|
+/*
 | 
						|
+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
 | 
						|
+ *
 | 
						|
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
 | 
						|
+ */
 | 
						|
+
 | 
						|
+#ifndef _MTK_SNAND_DEF_H_
 | 
						|
+#define _MTK_SNAND_DEF_H_
 | 
						|
+
 | 
						|
+#include "mtk-snand-os.h"
 | 
						|
+
 | 
						|
+#ifdef PRIVATE_MTK_SNAND_HEADER
 | 
						|
+#include "mtk-snand.h"
 | 
						|
+#else
 | 
						|
+#include <mtk-snand.h>
 | 
						|
+#endif
 | 
						|
+
 | 
						|
+struct mtk_snand_plat_dev;
 | 
						|
+
 | 
						|
+enum snand_flash_io {
 | 
						|
+	SNAND_IO_1_1_1,
 | 
						|
+	SNAND_IO_1_1_2,
 | 
						|
+	SNAND_IO_1_2_2,
 | 
						|
+	SNAND_IO_1_1_4,
 | 
						|
+	SNAND_IO_1_4_4,
 | 
						|
+
 | 
						|
+	__SNAND_IO_MAX
 | 
						|
+};
 | 
						|
+
 | 
						|
+#define SPI_IO_1_1_1			BIT(SNAND_IO_1_1_1)
 | 
						|
+#define SPI_IO_1_1_2			BIT(SNAND_IO_1_1_2)
 | 
						|
+#define SPI_IO_1_2_2			BIT(SNAND_IO_1_2_2)
 | 
						|
+#define SPI_IO_1_1_4			BIT(SNAND_IO_1_1_4)
 | 
						|
+#define SPI_IO_1_4_4			BIT(SNAND_IO_1_4_4)
 | 
						|
+
 | 
						|
+struct snand_opcode {
 | 
						|
+	uint8_t opcode;
 | 
						|
+	uint8_t dummy;
 | 
						|
+};
 | 
						|
+
 | 
						|
+struct snand_io_cap {
 | 
						|
+	uint8_t caps;
 | 
						|
+	struct snand_opcode opcodes[__SNAND_IO_MAX];
 | 
						|
+};
 | 
						|
+
 | 
						|
+#define SNAND_OP(_io, _opcode, _dummy) [_io] = { .opcode = (_opcode), \
 | 
						|
+						 .dummy = (_dummy) }
 | 
						|
+
 | 
						|
+#define SNAND_IO_CAP(_name, _caps, ...) \
 | 
						|
+	struct snand_io_cap _name = { .caps = (_caps), \
 | 
						|
+				      .opcodes = { __VA_ARGS__ } }
 | 
						|
+
 | 
						|
+#define SNAND_MAX_ID_LEN		4
 | 
						|
+
 | 
						|
+enum snand_id_type {
 | 
						|
+	SNAND_ID_DYMMY,
 | 
						|
+	SNAND_ID_ADDR = SNAND_ID_DYMMY,
 | 
						|
+	SNAND_ID_DIRECT,
 | 
						|
+
 | 
						|
+	__SNAND_ID_TYPE_MAX
 | 
						|
+};
 | 
						|
+
 | 
						|
+struct snand_id {
 | 
						|
+	uint8_t type;	/* enum snand_id_type */
 | 
						|
+	uint8_t len;
 | 
						|
+	uint8_t id[SNAND_MAX_ID_LEN];
 | 
						|
+};
 | 
						|
+
 | 
						|
+#define SNAND_ID(_type, ...) \
 | 
						|
+	{ .type = (_type), .id = { __VA_ARGS__ }, \
 | 
						|
+	  .len = sizeof((uint8_t[]) { __VA_ARGS__ }) }
 | 
						|
+
 | 
						|
+struct snand_mem_org {
 | 
						|
+	uint16_t pagesize;
 | 
						|
+	uint16_t sparesize;
 | 
						|
+	uint16_t pages_per_block;
 | 
						|
+	uint16_t blocks_per_die;
 | 
						|
+	uint16_t planes_per_die;
 | 
						|
+	uint16_t ndies;
 | 
						|
+};
 | 
						|
+
 | 
						|
+#define SNAND_MEMORG(_ps, _ss, _ppb, _bpd, _ppd, _nd) \
 | 
						|
+	{ .pagesize = (_ps), .sparesize = (_ss), .pages_per_block = (_ppb), \
 | 
						|
+	  .blocks_per_die = (_bpd), .planes_per_die = (_ppd), .ndies = (_nd) }
 | 
						|
+
 | 
						|
+typedef int (*snand_select_die_t)(struct mtk_snand *snf, uint32_t dieidx);
 | 
						|
+
 | 
						|
+struct snand_flash_info {
 | 
						|
+	const char *model;
 | 
						|
+	struct snand_id id;
 | 
						|
+	const struct snand_mem_org memorg;
 | 
						|
+	const struct snand_io_cap *cap_rd;
 | 
						|
+	const struct snand_io_cap *cap_pl;
 | 
						|
+	snand_select_die_t select_die;
 | 
						|
+};
 | 
						|
+
 | 
						|
+#define SNAND_INFO(_model, _id, _memorg, _cap_rd, _cap_pl, ...) \
 | 
						|
+	{ .model = (_model), .id = _id, .memorg = _memorg, \
 | 
						|
+	  .cap_rd = (_cap_rd), .cap_pl = (_cap_pl), __VA_ARGS__ }
 | 
						|
+
 | 
						|
+const struct snand_flash_info *snand_flash_id_lookup(enum snand_id_type type,
 | 
						|
+						     const uint8_t *id);
 | 
						|
+
 | 
						|
+struct mtk_snand_soc_data {
 | 
						|
+	uint16_t sector_size;
 | 
						|
+	uint16_t max_sectors;
 | 
						|
+	uint16_t fdm_size;
 | 
						|
+	uint16_t fdm_ecc_size;
 | 
						|
+	uint16_t fifo_size;
 | 
						|
+
 | 
						|
+	bool bbm_swap;
 | 
						|
+	bool empty_page_check;
 | 
						|
+	uint32_t mastersta_mask;
 | 
						|
+
 | 
						|
+	const uint8_t *spare_sizes;
 | 
						|
+	uint32_t num_spare_size;
 | 
						|
+
 | 
						|
+	uint16_t latch_lat;
 | 
						|
+	uint16_t sample_delay;
 | 
						|
+};
 | 
						|
+
 | 
						|
+enum mtk_ecc_regs {
 | 
						|
+	ECC_DECDONE,
 | 
						|
+};
 | 
						|
+
 | 
						|
+struct mtk_ecc_soc_data {
 | 
						|
+	const uint8_t *ecc_caps;
 | 
						|
+	uint32_t num_ecc_cap;
 | 
						|
+	const uint32_t *regs;
 | 
						|
+	uint16_t mode_shift;
 | 
						|
+	uint8_t errnum_bits;
 | 
						|
+	uint8_t errnum_shift;
 | 
						|
+};
 | 
						|
+
 | 
						|
+struct mtk_snand {
 | 
						|
+	struct mtk_snand_plat_dev *pdev;
 | 
						|
+
 | 
						|
+	void __iomem *nfi_base;
 | 
						|
+	void __iomem *ecc_base;
 | 
						|
+
 | 
						|
+	enum mtk_snand_soc soc;
 | 
						|
+	const struct mtk_snand_soc_data *nfi_soc;
 | 
						|
+	const struct mtk_ecc_soc_data *ecc_soc;
 | 
						|
+	bool snfi_quad_spi;
 | 
						|
+	bool quad_spi_op;
 | 
						|
+
 | 
						|
+	const char *model;
 | 
						|
+	uint64_t size;
 | 
						|
+	uint64_t die_size;
 | 
						|
+	uint32_t erasesize;
 | 
						|
+	uint32_t writesize;
 | 
						|
+	uint32_t oobsize;
 | 
						|
+
 | 
						|
+	uint32_t num_dies;
 | 
						|
+	snand_select_die_t select_die;
 | 
						|
+
 | 
						|
+	uint8_t opcode_rfc;
 | 
						|
+	uint8_t opcode_pl;
 | 
						|
+	uint8_t dummy_rfc;
 | 
						|
+	uint8_t mode_rfc;
 | 
						|
+	uint8_t mode_pl;
 | 
						|
+
 | 
						|
+	uint32_t writesize_mask;
 | 
						|
+	uint32_t writesize_shift;
 | 
						|
+	uint32_t erasesize_mask;
 | 
						|
+	uint32_t erasesize_shift;
 | 
						|
+	uint64_t die_mask;
 | 
						|
+	uint32_t die_shift;
 | 
						|
+
 | 
						|
+	uint32_t spare_per_sector;
 | 
						|
+	uint32_t raw_sector_size;
 | 
						|
+	uint32_t ecc_strength;
 | 
						|
+	uint32_t ecc_steps;
 | 
						|
+	uint32_t ecc_bytes;
 | 
						|
+	uint32_t ecc_parity_bits;
 | 
						|
+
 | 
						|
+	uint8_t *page_cache;	/* Used by read/write page */
 | 
						|
+	uint8_t *buf_cache;	/* Used by block bad/markbad & auto_oob */
 | 
						|
+	int *sect_bf;		/* Used by ECC correction */
 | 
						|
+};
 | 
						|
+
 | 
						|
+enum mtk_snand_log_category {
 | 
						|
+	SNAND_LOG_NFI,
 | 
						|
+	SNAND_LOG_SNFI,
 | 
						|
+	SNAND_LOG_ECC,
 | 
						|
+	SNAND_LOG_CHIP,
 | 
						|
+
 | 
						|
+	__SNAND_LOG_CAT_MAX
 | 
						|
+};
 | 
						|
+
 | 
						|
+int mtk_ecc_setup(struct mtk_snand *snf, void *fmdaddr, uint32_t max_ecc_bytes,
 | 
						|
+		  uint32_t msg_size);
 | 
						|
+int mtk_snand_ecc_encoder_start(struct mtk_snand *snf);
 | 
						|
+void mtk_snand_ecc_encoder_stop(struct mtk_snand *snf);
 | 
						|
+int mtk_snand_ecc_decoder_start(struct mtk_snand *snf);
 | 
						|
+void mtk_snand_ecc_decoder_stop(struct mtk_snand *snf);
 | 
						|
+int mtk_ecc_wait_decoder_done(struct mtk_snand *snf);
 | 
						|
+int mtk_ecc_check_decode_error(struct mtk_snand *snf);
 | 
						|
+int mtk_ecc_fixup_empty_sector(struct mtk_snand *snf, uint32_t sect);
 | 
						|
+
 | 
						|
+int mtk_snand_mac_io(struct mtk_snand *snf, const uint8_t *out, uint32_t outlen,
 | 
						|
+		     uint8_t *in, uint32_t inlen);
 | 
						|
+int mtk_snand_set_feature(struct mtk_snand *snf, uint32_t addr, uint32_t val);
 | 
						|
+
 | 
						|
+int mtk_snand_log(struct mtk_snand_plat_dev *pdev,
 | 
						|
+		  enum mtk_snand_log_category cat, const char *fmt, ...);
 | 
						|
+
 | 
						|
+#define snand_log_nfi(pdev, fmt, ...) \
 | 
						|
+	mtk_snand_log(pdev, SNAND_LOG_NFI, fmt, ##__VA_ARGS__)
 | 
						|
+
 | 
						|
+#define snand_log_snfi(pdev, fmt, ...) \
 | 
						|
+	mtk_snand_log(pdev, SNAND_LOG_SNFI, fmt, ##__VA_ARGS__)
 | 
						|
+
 | 
						|
+#define snand_log_ecc(pdev, fmt, ...) \
 | 
						|
+	mtk_snand_log(pdev, SNAND_LOG_ECC, fmt, ##__VA_ARGS__)
 | 
						|
+
 | 
						|
+#define snand_log_chip(pdev, fmt, ...) \
 | 
						|
+	mtk_snand_log(pdev, SNAND_LOG_CHIP, fmt, ##__VA_ARGS__)
 | 
						|
+
 | 
						|
+/* ffs64 */
 | 
						|
+static inline int mtk_snand_ffs64(uint64_t x)
 | 
						|
+{
 | 
						|
+	if (!x)
 | 
						|
+		return 0;
 | 
						|
+
 | 
						|
+	if (!(x & 0xffffffff))
 | 
						|
+		return ffs((uint32_t)(x >> 32)) + 32;
 | 
						|
+
 | 
						|
+	return ffs((uint32_t)(x & 0xffffffff));
 | 
						|
+}
 | 
						|
+
 | 
						|
+/* NFI dummy commands */
 | 
						|
+#define NFI_CMD_DUMMY_READ		0x00
 | 
						|
+#define NFI_CMD_DUMMY_WRITE		0x80
 | 
						|
+
 | 
						|
+/* SPI-NAND opcodes */
 | 
						|
+#define SNAND_CMD_RESET			0xff
 | 
						|
+#define SNAND_CMD_BLOCK_ERASE		0xd8
 | 
						|
+#define SNAND_CMD_READ_FROM_CACHE_QUAD	0xeb
 | 
						|
+#define SNAND_CMD_WINBOND_SELECT_DIE	0xc2
 | 
						|
+#define SNAND_CMD_READ_FROM_CACHE_DUAL	0xbb
 | 
						|
+#define SNAND_CMD_READID		0x9f
 | 
						|
+#define SNAND_CMD_READ_FROM_CACHE_X4	0x6b
 | 
						|
+#define SNAND_CMD_READ_FROM_CACHE_X2	0x3b
 | 
						|
+#define SNAND_CMD_PROGRAM_LOAD_X4	0x32
 | 
						|
+#define SNAND_CMD_SET_FEATURE		0x1f
 | 
						|
+#define SNAND_CMD_READ_TO_CACHE		0x13
 | 
						|
+#define SNAND_CMD_PROGRAM_EXECUTE	0x10
 | 
						|
+#define SNAND_CMD_GET_FEATURE		0x0f
 | 
						|
+#define SNAND_CMD_READ_FROM_CACHE	0x0b
 | 
						|
+#define SNAND_CMD_WRITE_ENABLE		0x06
 | 
						|
+#define SNAND_CMD_PROGRAM_LOAD		0x02
 | 
						|
+
 | 
						|
+/* SPI-NAND feature addresses */
 | 
						|
+#define SNAND_FEATURE_MICRON_DIE_ADDR	0xd0
 | 
						|
+#define SNAND_MICRON_DIE_SEL_1		BIT(6)
 | 
						|
+
 | 
						|
+#define SNAND_FEATURE_STATUS_ADDR	0xc0
 | 
						|
+#define SNAND_STATUS_OIP		BIT(0)
 | 
						|
+#define SNAND_STATUS_WEL		BIT(1)
 | 
						|
+#define SNAND_STATUS_ERASE_FAIL		BIT(2)
 | 
						|
+#define SNAND_STATUS_PROGRAM_FAIL	BIT(3)
 | 
						|
+
 | 
						|
+#define SNAND_FEATURE_CONFIG_ADDR	0xb0
 | 
						|
+#define SNAND_FEATURE_QUAD_ENABLE	BIT(0)
 | 
						|
+#define SNAND_FEATURE_ECC_EN		BIT(4)
 | 
						|
+
 | 
						|
+#define SNAND_FEATURE_PROTECT_ADDR	0xa0
 | 
						|
+
 | 
						|
+#endif /* _MTK_SNAND_DEF_H_ */
 | 
						|
--- /dev/null
 | 
						|
+++ b/drivers/mtd/mtk-snand/mtk-snand-ecc.c
 | 
						|
@@ -0,0 +1,395 @@
 | 
						|
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
 | 
						|
+/*
 | 
						|
+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
 | 
						|
+ *
 | 
						|
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
 | 
						|
+ */
 | 
						|
+
 | 
						|
+#include "mtk-snand-def.h"
 | 
						|
+
 | 
						|
+/* ECC registers */
 | 
						|
+#define ECC_ENCCON			0x000
 | 
						|
+#define ENC_EN				BIT(0)
 | 
						|
+
 | 
						|
+#define ECC_ENCCNFG			0x004
 | 
						|
+#define ENC_MS_S			16
 | 
						|
+#define ENC_BURST_EN			BIT(8)
 | 
						|
+#define ENC_TNUM_S			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_S			16
 | 
						|
+#define DEC_CON_S			12
 | 
						|
+#define   DEC_CON_CORRECT		3
 | 
						|
+#define DEC_BURST_EN			BIT(8)
 | 
						|
+#define DEC_TNUM_S			0
 | 
						|
+
 | 
						|
+#define ECC_DECIDLE			0x10c
 | 
						|
+#define DEC_IDLE			BIT(0)
 | 
						|
+
 | 
						|
+#define ECC_DECENUM0			0x114
 | 
						|
+#define ECC_DECENUM(n)			(ECC_DECENUM0 + (n) * 4)
 | 
						|
+
 | 
						|
+/* ECC_ENCIDLE & ECC_DECIDLE */
 | 
						|
+#define ECC_IDLE			BIT(0)
 | 
						|
+
 | 
						|
+/* ENC_MODE & DEC_MODE */
 | 
						|
+#define ECC_MODE_NFI			1
 | 
						|
+
 | 
						|
+#define ECC_TIMEOUT			500000
 | 
						|
+
 | 
						|
+static const uint8_t mt7622_ecc_caps[] = { 4, 6, 8, 10, 12 };
 | 
						|
+
 | 
						|
+static const uint8_t mt7986_ecc_caps[] = {
 | 
						|
+	4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24
 | 
						|
+};
 | 
						|
+
 | 
						|
+static const uint32_t mt7622_ecc_regs[] = {
 | 
						|
+	[ECC_DECDONE] = 0x11c,
 | 
						|
+};
 | 
						|
+
 | 
						|
+static const uint32_t mt7986_ecc_regs[] = {
 | 
						|
+	[ECC_DECDONE] = 0x124,
 | 
						|
+};
 | 
						|
+
 | 
						|
+static const struct mtk_ecc_soc_data mtk_ecc_socs[__SNAND_SOC_MAX] = {
 | 
						|
+	[SNAND_SOC_MT7622] = {
 | 
						|
+		.ecc_caps = mt7622_ecc_caps,
 | 
						|
+		.num_ecc_cap = ARRAY_SIZE(mt7622_ecc_caps),
 | 
						|
+		.regs = mt7622_ecc_regs,
 | 
						|
+		.mode_shift = 4,
 | 
						|
+		.errnum_bits = 5,
 | 
						|
+		.errnum_shift = 5,
 | 
						|
+	},
 | 
						|
+	[SNAND_SOC_MT7629] = {
 | 
						|
+		.ecc_caps = mt7622_ecc_caps,
 | 
						|
+		.num_ecc_cap = ARRAY_SIZE(mt7622_ecc_caps),
 | 
						|
+		.regs = mt7622_ecc_regs,
 | 
						|
+		.mode_shift = 4,
 | 
						|
+		.errnum_bits = 5,
 | 
						|
+		.errnum_shift = 5,
 | 
						|
+	},
 | 
						|
+	[SNAND_SOC_MT7986] = {
 | 
						|
+		.ecc_caps = mt7986_ecc_caps,
 | 
						|
+		.num_ecc_cap = ARRAY_SIZE(mt7986_ecc_caps),
 | 
						|
+		.regs = mt7986_ecc_regs,
 | 
						|
+		.mode_shift = 5,
 | 
						|
+		.errnum_bits = 5,
 | 
						|
+		.errnum_shift = 8,
 | 
						|
+	},
 | 
						|
+};
 | 
						|
+
 | 
						|
+static inline uint32_t ecc_read32(struct mtk_snand *snf, uint32_t reg)
 | 
						|
+{
 | 
						|
+	return readl(snf->ecc_base + reg);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline void ecc_write32(struct mtk_snand *snf, uint32_t reg,
 | 
						|
+			       uint32_t val)
 | 
						|
+{
 | 
						|
+	writel(val, snf->ecc_base + reg);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline void ecc_write16(struct mtk_snand *snf, uint32_t reg,
 | 
						|
+			       uint16_t val)
 | 
						|
+{
 | 
						|
+	writew(val, snf->ecc_base + reg);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_ecc_poll(struct mtk_snand *snf, uint32_t reg, uint32_t bits)
 | 
						|
+{
 | 
						|
+	uint32_t val;
 | 
						|
+
 | 
						|
+	return read16_poll_timeout(snf->ecc_base + reg, val, (val & bits), 0,
 | 
						|
+				   ECC_TIMEOUT);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_ecc_wait_idle(struct mtk_snand *snf, uint32_t reg)
 | 
						|
+{
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	ret = mtk_ecc_poll(snf, reg, ECC_IDLE);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_ecc(snf->pdev, "ECC engine is busy\n");
 | 
						|
+		return -EBUSY;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_ecc_setup(struct mtk_snand *snf, void *fmdaddr, uint32_t max_ecc_bytes,
 | 
						|
+		  uint32_t msg_size)
 | 
						|
+{
 | 
						|
+	uint32_t i, val, ecc_msg_bits, ecc_strength;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	snf->ecc_soc = &mtk_ecc_socs[snf->soc];
 | 
						|
+
 | 
						|
+	snf->ecc_parity_bits = fls(1 + 8 * msg_size);
 | 
						|
+	ecc_strength = max_ecc_bytes * 8 / snf->ecc_parity_bits;
 | 
						|
+
 | 
						|
+	for (i = snf->ecc_soc->num_ecc_cap - 1; i >= 0; i--) {
 | 
						|
+		if (snf->ecc_soc->ecc_caps[i] <= ecc_strength)
 | 
						|
+			break;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (unlikely(i < 0)) {
 | 
						|
+		snand_log_ecc(snf->pdev, "Page size %u+%u is not supported\n",
 | 
						|
+			      snf->writesize, snf->oobsize);
 | 
						|
+		return -ENOTSUPP;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	snf->ecc_strength = snf->ecc_soc->ecc_caps[i];
 | 
						|
+	snf->ecc_bytes = DIV_ROUND_UP(snf->ecc_strength * snf->ecc_parity_bits,
 | 
						|
+				      8);
 | 
						|
+
 | 
						|
+	/* Encoder config */
 | 
						|
+	ecc_write16(snf, ECC_ENCCON, 0);
 | 
						|
+	ret = mtk_ecc_wait_idle(snf, ECC_ENCIDLE);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	ecc_msg_bits = msg_size * 8;
 | 
						|
+	val = (ecc_msg_bits << ENC_MS_S) |
 | 
						|
+	      (ECC_MODE_NFI << snf->ecc_soc->mode_shift) | i;
 | 
						|
+	ecc_write32(snf, ECC_ENCCNFG, val);
 | 
						|
+
 | 
						|
+	/* Decoder config */
 | 
						|
+	ecc_write16(snf, ECC_DECCON, 0);
 | 
						|
+	ret = mtk_ecc_wait_idle(snf, ECC_DECIDLE);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	ecc_msg_bits += snf->ecc_strength * snf->ecc_parity_bits;
 | 
						|
+	val = DEC_EMPTY_EN | (ecc_msg_bits << DEC_CS_S) |
 | 
						|
+	      (DEC_CON_CORRECT << DEC_CON_S) |
 | 
						|
+	      (ECC_MODE_NFI << snf->ecc_soc->mode_shift) | i;
 | 
						|
+	ecc_write32(snf, ECC_DECCNFG, val);
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_ecc_encoder_start(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	ret = mtk_ecc_wait_idle(snf, ECC_ENCIDLE);
 | 
						|
+	if (ret) {
 | 
						|
+		ecc_write16(snf, ECC_ENCCON, 0);
 | 
						|
+		mtk_ecc_wait_idle(snf, ECC_ENCIDLE);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	ecc_write16(snf, ECC_ENCCON, ENC_EN);
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+void mtk_snand_ecc_encoder_stop(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	mtk_ecc_wait_idle(snf, ECC_ENCIDLE);
 | 
						|
+	ecc_write16(snf, ECC_ENCCON, 0);
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_ecc_decoder_start(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	ret = mtk_ecc_wait_idle(snf, ECC_DECIDLE);
 | 
						|
+	if (ret) {
 | 
						|
+		ecc_write16(snf, ECC_DECCON, 0);
 | 
						|
+		mtk_ecc_wait_idle(snf, ECC_DECIDLE);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	ecc_write16(snf, ECC_DECCON, DEC_EN);
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+void mtk_snand_ecc_decoder_stop(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	mtk_ecc_wait_idle(snf, ECC_DECIDLE);
 | 
						|
+	ecc_write16(snf, ECC_DECCON, 0);
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_ecc_wait_decoder_done(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	uint16_t val, step_mask = (1 << snf->ecc_steps) - 1;
 | 
						|
+	uint32_t reg = snf->ecc_soc->regs[ECC_DECDONE];
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	ret = read16_poll_timeout(snf->ecc_base + reg, val,
 | 
						|
+				  (val & step_mask) == step_mask, 0,
 | 
						|
+				  ECC_TIMEOUT);
 | 
						|
+	if (ret)
 | 
						|
+		snand_log_ecc(snf->pdev, "ECC decoder is busy\n");
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_ecc_check_decode_error(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	uint32_t i, regi, fi, errnum;
 | 
						|
+	uint32_t errnum_shift = snf->ecc_soc->errnum_shift;
 | 
						|
+	uint32_t errnum_mask = (1 << snf->ecc_soc->errnum_bits) - 1;
 | 
						|
+	int ret = 0;
 | 
						|
+
 | 
						|
+	for (i = 0; i < snf->ecc_steps; i++) {
 | 
						|
+		regi = i / 4;
 | 
						|
+		fi = i % 4;
 | 
						|
+
 | 
						|
+		errnum = ecc_read32(snf, ECC_DECENUM(regi));
 | 
						|
+		errnum = (errnum >> (fi * errnum_shift)) & errnum_mask;
 | 
						|
+
 | 
						|
+		if (errnum <= snf->ecc_strength) {
 | 
						|
+			snf->sect_bf[i] = errnum;
 | 
						|
+		} else {
 | 
						|
+			snf->sect_bf[i] = -1;
 | 
						|
+			ret = -EBADMSG;
 | 
						|
+		}
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_ecc_check_buf_bitflips(struct mtk_snand *snf, const void *buf,
 | 
						|
+				      size_t len, uint32_t bitflips)
 | 
						|
+{
 | 
						|
+	const uint8_t *buf8 = buf;
 | 
						|
+	const uint32_t *buf32;
 | 
						|
+	uint32_t d, weight;
 | 
						|
+
 | 
						|
+	while (len && ((uintptr_t)buf8) % sizeof(uint32_t)) {
 | 
						|
+		weight = hweight8(*buf8);
 | 
						|
+		bitflips += BITS_PER_BYTE - weight;
 | 
						|
+		buf8++;
 | 
						|
+		len--;
 | 
						|
+
 | 
						|
+		if (bitflips > snf->ecc_strength)
 | 
						|
+			return -EBADMSG;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	buf32 = (const uint32_t *)buf8;
 | 
						|
+	while (len >= sizeof(uint32_t)) {
 | 
						|
+		d = *buf32;
 | 
						|
+
 | 
						|
+		if (d != ~0) {
 | 
						|
+			weight = hweight32(d);
 | 
						|
+			bitflips += sizeof(uint32_t) * BITS_PER_BYTE - weight;
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		buf32++;
 | 
						|
+		len -= sizeof(uint32_t);
 | 
						|
+
 | 
						|
+		if (bitflips > snf->ecc_strength)
 | 
						|
+			return -EBADMSG;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	buf8 = (const uint8_t *)buf32;
 | 
						|
+	while (len) {
 | 
						|
+		weight = hweight8(*buf8);
 | 
						|
+		bitflips += BITS_PER_BYTE - weight;
 | 
						|
+		buf8++;
 | 
						|
+		len--;
 | 
						|
+
 | 
						|
+		if (bitflips > snf->ecc_strength)
 | 
						|
+			return -EBADMSG;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return bitflips;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_ecc_check_parity_bitflips(struct mtk_snand *snf, const void *buf,
 | 
						|
+					 uint32_t bits, uint32_t bitflips)
 | 
						|
+{
 | 
						|
+	uint32_t len, i;
 | 
						|
+	uint8_t b;
 | 
						|
+	int rc;
 | 
						|
+
 | 
						|
+	len = bits >> 3;
 | 
						|
+	bits &= 7;
 | 
						|
+
 | 
						|
+	rc = mtk_ecc_check_buf_bitflips(snf, buf, len, bitflips);
 | 
						|
+	if (!bits || rc < 0)
 | 
						|
+		return rc;
 | 
						|
+
 | 
						|
+	bitflips = rc;
 | 
						|
+
 | 
						|
+	/* We want a precise count of bits */
 | 
						|
+	b = ((const uint8_t *)buf)[len];
 | 
						|
+	for (i = 0; i < bits; i++) {
 | 
						|
+		if (!(b & BIT(i)))
 | 
						|
+			bitflips++;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (bitflips > snf->ecc_strength)
 | 
						|
+		return -EBADMSG;
 | 
						|
+
 | 
						|
+	return bitflips;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void mtk_ecc_reset_parity(void *buf, uint32_t bits)
 | 
						|
+{
 | 
						|
+	uint32_t len;
 | 
						|
+
 | 
						|
+	len = bits >> 3;
 | 
						|
+	bits &= 7;
 | 
						|
+
 | 
						|
+	memset(buf, 0xff, len);
 | 
						|
+
 | 
						|
+	/* Only reset bits protected by ECC to 1 */
 | 
						|
+	if (bits)
 | 
						|
+		((uint8_t *)buf)[len] |= GENMASK(bits - 1, 0);
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_ecc_fixup_empty_sector(struct mtk_snand *snf, uint32_t sect)
 | 
						|
+{
 | 
						|
+	uint32_t ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size;
 | 
						|
+	uint8_t *oob = snf->page_cache + snf->writesize;
 | 
						|
+	uint8_t *data_ptr, *fdm_ptr, *ecc_ptr;
 | 
						|
+	int bitflips = 0, ecc_bits, parity_bits;
 | 
						|
+
 | 
						|
+	parity_bits = fls(snf->nfi_soc->sector_size * 8);
 | 
						|
+	ecc_bits = snf->ecc_strength * parity_bits;
 | 
						|
+
 | 
						|
+	data_ptr = snf->page_cache + sect * snf->nfi_soc->sector_size;
 | 
						|
+	fdm_ptr = oob + sect * snf->nfi_soc->fdm_size;
 | 
						|
+	ecc_ptr = oob + snf->ecc_steps * snf->nfi_soc->fdm_size +
 | 
						|
+		  sect * ecc_bytes;
 | 
						|
+
 | 
						|
+	/*
 | 
						|
+	 * Check whether DATA + FDM + ECC of a sector contains correctable
 | 
						|
+	 * bitflips
 | 
						|
+	 */
 | 
						|
+	bitflips = mtk_ecc_check_buf_bitflips(snf, data_ptr,
 | 
						|
+					      snf->nfi_soc->sector_size,
 | 
						|
+					      bitflips);
 | 
						|
+	if (bitflips < 0)
 | 
						|
+		return -EBADMSG;
 | 
						|
+
 | 
						|
+	bitflips = mtk_ecc_check_buf_bitflips(snf, fdm_ptr,
 | 
						|
+					      snf->nfi_soc->fdm_ecc_size,
 | 
						|
+					      bitflips);
 | 
						|
+	if (bitflips < 0)
 | 
						|
+		return -EBADMSG;
 | 
						|
+
 | 
						|
+	bitflips = mtk_ecc_check_parity_bitflips(snf, ecc_ptr, ecc_bits,
 | 
						|
+						 bitflips);
 | 
						|
+	if (bitflips < 0)
 | 
						|
+		return -EBADMSG;
 | 
						|
+
 | 
						|
+	if (!bitflips)
 | 
						|
+		return 0;
 | 
						|
+
 | 
						|
+	/* Reset the data of this sector to 0xff */
 | 
						|
+	memset(data_ptr, 0xff, snf->nfi_soc->sector_size);
 | 
						|
+	memset(fdm_ptr, 0xff, snf->nfi_soc->fdm_ecc_size);
 | 
						|
+	mtk_ecc_reset_parity(ecc_ptr, ecc_bits);
 | 
						|
+
 | 
						|
+	return bitflips;
 | 
						|
+}
 | 
						|
--- /dev/null
 | 
						|
+++ b/drivers/mtd/mtk-snand/mtk-snand-ids.c
 | 
						|
@@ -0,0 +1,511 @@
 | 
						|
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
 | 
						|
+/*
 | 
						|
+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
 | 
						|
+ *
 | 
						|
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
 | 
						|
+ */
 | 
						|
+
 | 
						|
+#include "mtk-snand-def.h"
 | 
						|
+
 | 
						|
+static int mtk_snand_winbond_select_die(struct mtk_snand *snf, uint32_t dieidx);
 | 
						|
+static int mtk_snand_micron_select_die(struct mtk_snand *snf, uint32_t dieidx);
 | 
						|
+
 | 
						|
+#define SNAND_MEMORG_512M_2K_64		SNAND_MEMORG(2048, 64, 64, 512, 1, 1)
 | 
						|
+#define SNAND_MEMORG_1G_2K_64		SNAND_MEMORG(2048, 64, 64, 1024, 1, 1)
 | 
						|
+#define SNAND_MEMORG_2G_2K_64		SNAND_MEMORG(2048, 64, 64, 2048, 1, 1)
 | 
						|
+#define SNAND_MEMORG_2G_2K_120		SNAND_MEMORG(2048, 120, 64, 2048, 1, 1)
 | 
						|
+#define SNAND_MEMORG_4G_2K_64		SNAND_MEMORG(2048, 64, 64, 4096, 1, 1)
 | 
						|
+#define SNAND_MEMORG_1G_2K_120		SNAND_MEMORG(2048, 120, 64, 1024, 1, 1)
 | 
						|
+#define SNAND_MEMORG_1G_2K_128		SNAND_MEMORG(2048, 128, 64, 1024, 1, 1)
 | 
						|
+#define SNAND_MEMORG_2G_2K_128		SNAND_MEMORG(2048, 128, 64, 2048, 1, 1)
 | 
						|
+#define SNAND_MEMORG_4G_2K_128		SNAND_MEMORG(2048, 128, 64, 4096, 1, 1)
 | 
						|
+#define SNAND_MEMORG_4G_4K_240		SNAND_MEMORG(4096, 240, 64, 2048, 1, 1)
 | 
						|
+#define SNAND_MEMORG_4G_4K_256		SNAND_MEMORG(4096, 256, 64, 2048, 1, 1)
 | 
						|
+#define SNAND_MEMORG_8G_4K_256		SNAND_MEMORG(4096, 256, 64, 4096, 1, 1)
 | 
						|
+#define SNAND_MEMORG_2G_2K_64_2P	SNAND_MEMORG(2048, 64, 64, 2048, 2, 1)
 | 
						|
+#define SNAND_MEMORG_2G_2K_64_2D	SNAND_MEMORG(2048, 64, 64, 1024, 1, 2)
 | 
						|
+#define SNAND_MEMORG_2G_2K_128_2P	SNAND_MEMORG(2048, 128, 64, 2048, 2, 1)
 | 
						|
+#define SNAND_MEMORG_4G_2K_64_2P	SNAND_MEMORG(2048, 64, 64, 4096, 2, 1)
 | 
						|
+#define SNAND_MEMORG_4G_2K_128_2P_2D	SNAND_MEMORG(2048, 128, 64, 2048, 2, 2)
 | 
						|
+#define SNAND_MEMORG_8G_4K_256_2D	SNAND_MEMORG(4096, 256, 64, 2048, 1, 2)
 | 
						|
+
 | 
						|
+static const SNAND_IO_CAP(snand_cap_read_from_cache_quad,
 | 
						|
+	SPI_IO_1_1_1 | SPI_IO_1_1_2 | SPI_IO_1_2_2 | SPI_IO_1_1_4 |
 | 
						|
+	SPI_IO_1_4_4,
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_READ_FROM_CACHE, 8),
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_2, SNAND_CMD_READ_FROM_CACHE_X2, 8),
 | 
						|
+	SNAND_OP(SNAND_IO_1_2_2, SNAND_CMD_READ_FROM_CACHE_DUAL, 4),
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_4, SNAND_CMD_READ_FROM_CACHE_X4, 8),
 | 
						|
+	SNAND_OP(SNAND_IO_1_4_4, SNAND_CMD_READ_FROM_CACHE_QUAD, 4));
 | 
						|
+
 | 
						|
+static const SNAND_IO_CAP(snand_cap_read_from_cache_quad_q2d,
 | 
						|
+	SPI_IO_1_1_1 | SPI_IO_1_1_2 | SPI_IO_1_2_2 | SPI_IO_1_1_4 |
 | 
						|
+	SPI_IO_1_4_4,
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_READ_FROM_CACHE, 8),
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_2, SNAND_CMD_READ_FROM_CACHE_X2, 8),
 | 
						|
+	SNAND_OP(SNAND_IO_1_2_2, SNAND_CMD_READ_FROM_CACHE_DUAL, 4),
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_4, SNAND_CMD_READ_FROM_CACHE_X4, 8),
 | 
						|
+	SNAND_OP(SNAND_IO_1_4_4, SNAND_CMD_READ_FROM_CACHE_QUAD, 2));
 | 
						|
+
 | 
						|
+static const SNAND_IO_CAP(snand_cap_read_from_cache_quad_a8d,
 | 
						|
+	SPI_IO_1_1_1 | SPI_IO_1_1_2 | SPI_IO_1_2_2 | SPI_IO_1_1_4 |
 | 
						|
+	SPI_IO_1_4_4,
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_READ_FROM_CACHE, 8),
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_2, SNAND_CMD_READ_FROM_CACHE_X2, 8),
 | 
						|
+	SNAND_OP(SNAND_IO_1_2_2, SNAND_CMD_READ_FROM_CACHE_DUAL, 8),
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_4, SNAND_CMD_READ_FROM_CACHE_X4, 8),
 | 
						|
+	SNAND_OP(SNAND_IO_1_4_4, SNAND_CMD_READ_FROM_CACHE_QUAD, 8));
 | 
						|
+
 | 
						|
+static const SNAND_IO_CAP(snand_cap_read_from_cache_x4,
 | 
						|
+	SPI_IO_1_1_1 | SPI_IO_1_1_2 | SPI_IO_1_1_4,
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_READ_FROM_CACHE, 8),
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_2, SNAND_CMD_READ_FROM_CACHE_X2, 8),
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_4, SNAND_CMD_READ_FROM_CACHE_X4, 8));
 | 
						|
+
 | 
						|
+static const SNAND_IO_CAP(snand_cap_read_from_cache_x4_only,
 | 
						|
+	SPI_IO_1_1_1 | SPI_IO_1_1_4,
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_READ_FROM_CACHE, 8),
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_4, SNAND_CMD_READ_FROM_CACHE_X4, 8));
 | 
						|
+
 | 
						|
+static const SNAND_IO_CAP(snand_cap_program_load_x1,
 | 
						|
+	SPI_IO_1_1_1,
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_PROGRAM_LOAD, 0));
 | 
						|
+
 | 
						|
+static const SNAND_IO_CAP(snand_cap_program_load_x4,
 | 
						|
+	SPI_IO_1_1_1 | SPI_IO_1_1_4,
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_PROGRAM_LOAD, 0),
 | 
						|
+	SNAND_OP(SNAND_IO_1_1_4, SNAND_CMD_PROGRAM_LOAD_X4, 0));
 | 
						|
+
 | 
						|
+static const struct snand_flash_info snand_flash_ids[] = {
 | 
						|
+	SNAND_INFO("W25N512GV", SNAND_ID(SNAND_ID_DYMMY, 0xef, 0xaa, 0x20),
 | 
						|
+		   SNAND_MEMORG_512M_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("W25N01GV", SNAND_ID(SNAND_ID_DYMMY, 0xef, 0xaa, 0x21),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("W25M02GV", SNAND_ID(SNAND_ID_DYMMY, 0xef, 0xab, 0x21),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64_2D,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4,
 | 
						|
+		   mtk_snand_winbond_select_die),
 | 
						|
+	SNAND_INFO("W25N02KV", SNAND_ID(SNAND_ID_DYMMY, 0xef, 0xaa, 0x22),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+
 | 
						|
+	SNAND_INFO("GD5F1GQ4UAWxx", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0x10),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("GD5F1GQ4UExIG", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xd1),
 | 
						|
+		   SNAND_MEMORG_1G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("GD5F1GQ4UExxH", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xd9),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("GD5F1GQ4xAYIG", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xf1),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("GD5F2GQ4UExIG", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xd2),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("GD5F2GQ5UExxH", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0x32),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_a8d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("GD5F2GQ4xAYIG", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xf2),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("GD5F4GQ4UBxIG", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xd4),
 | 
						|
+		   SNAND_MEMORG_4G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("GD5F4GQ4xAYIG", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xf4),
 | 
						|
+		   SNAND_MEMORG_4G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("GD5F2GQ5UExxG", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x52),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("GD5F4GQ4UCxIG", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0xb4),
 | 
						|
+		   SNAND_MEMORG_4G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+
 | 
						|
+	SNAND_INFO("MX35LF1GE4AB", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x12),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("MX35LF1G24AD", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x14),
 | 
						|
+		   SNAND_MEMORG_1G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("MX31LF1GE4BC", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x1e),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("MX35LF2GE4AB", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x22),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("MX35LF2G24AD", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x24),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("MX35LF2GE4AD", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x26),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("MX35LF2G14AC", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x20),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("MX35LF4G24AD", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x35),
 | 
						|
+		   SNAND_MEMORG_4G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("MX35LF4GE4AD", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x37),
 | 
						|
+		   SNAND_MEMORG_4G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+
 | 
						|
+	SNAND_INFO("MT29F1G01AAADD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x12),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x1),
 | 
						|
+	SNAND_INFO("MT29F1G01ABAFD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x14),
 | 
						|
+		   SNAND_MEMORG_1G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("MT29F2G01AAAED", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x9f),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64_2P,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x1),
 | 
						|
+	SNAND_INFO("MT29F2G01ABAGD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x24),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128_2P,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("MT29F4G01AAADD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x32),
 | 
						|
+		   SNAND_MEMORG_4G_2K_64_2P,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x1),
 | 
						|
+	SNAND_INFO("MT29F4G01ABAFD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x34),
 | 
						|
+		   SNAND_MEMORG_4G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("MT29F4G01ADAGD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x36),
 | 
						|
+		   SNAND_MEMORG_4G_2K_128_2P_2D,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4,
 | 
						|
+		   mtk_snand_micron_select_die),
 | 
						|
+	SNAND_INFO("MT29F8G01ADAFD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x46),
 | 
						|
+		   SNAND_MEMORG_8G_4K_256_2D,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4,
 | 
						|
+		   mtk_snand_micron_select_die),
 | 
						|
+
 | 
						|
+	SNAND_INFO("TC58CVG0S3HRAIG", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xc2),
 | 
						|
+		   SNAND_MEMORG_1G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x1),
 | 
						|
+	SNAND_INFO("TC58CVG1S3HRAIG", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xcb),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x1),
 | 
						|
+	SNAND_INFO("TC58CVG2S0HRAIG", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xcd),
 | 
						|
+		   SNAND_MEMORG_4G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x1),
 | 
						|
+	SNAND_INFO("TC58CVG0S3HRAIJ", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xe2),
 | 
						|
+		   SNAND_MEMORG_1G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("TC58CVG1S3HRAIJ", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xeb),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("TC58CVG2S0HRAIJ", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xed),
 | 
						|
+		   SNAND_MEMORG_4G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("TH58CVG3S0HRAIJ", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xe4),
 | 
						|
+		   SNAND_MEMORG_8G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+
 | 
						|
+	SNAND_INFO("F50L512M41A", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x20),
 | 
						|
+		   SNAND_MEMORG_512M_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("F50L1G41A", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x21),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("F50L1G41LB", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x01),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("F50L2G41LB", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x0a),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64_2D,
 | 
						|
+		   &snand_cap_read_from_cache_quad,
 | 
						|
+		   &snand_cap_program_load_x4,
 | 
						|
+		   mtk_snand_winbond_select_die),
 | 
						|
+
 | 
						|
+	SNAND_INFO("CS11G0T0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x00),
 | 
						|
+		   SNAND_MEMORG_1G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("CS11G0G0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x10),
 | 
						|
+		   SNAND_MEMORG_1G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("CS11G0S0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x20),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("CS11G1T0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x01),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("CS11G1S0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x21),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("CS11G2T0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x02),
 | 
						|
+		   SNAND_MEMORG_4G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("CS11G2S0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x22),
 | 
						|
+		   SNAND_MEMORG_4G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+
 | 
						|
+	SNAND_INFO("EM73B044VCA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x01),
 | 
						|
+		   SNAND_MEMORG_512M_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73C044SNB", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x11),
 | 
						|
+		   SNAND_MEMORG_1G_2K_120,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73C044SNF", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x09),
 | 
						|
+		   SNAND_MEMORG_1G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73C044VCA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x18),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73C044SNA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x19),
 | 
						|
+		   SNAND_MEMORG(2048, 64, 128, 512, 1, 1),
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73C044VCD", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x1c),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73C044SND", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x1d),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044SND", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x1e),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73C044VCC", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x22),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73C044VCF", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x25),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73C044SNC", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x31),
 | 
						|
+		   SNAND_MEMORG_1G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044SNC", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x0a),
 | 
						|
+		   SNAND_MEMORG_2G_2K_120,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044SNA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x12),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044SNF", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x10),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044VCA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x13),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044VCB", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x14),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044VCD", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x17),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044VCH", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x1b),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044SND", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x1d),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044VCG", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x1f),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044VCE", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x20),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044VCL", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x2e),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044SNB", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x32),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73E044SNA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x03),
 | 
						|
+		   SNAND_MEMORG_4G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73E044SND", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x0b),
 | 
						|
+		   SNAND_MEMORG_4G_4K_240,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73E044SNB", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x23),
 | 
						|
+		   SNAND_MEMORG_4G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73E044VCA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x2c),
 | 
						|
+		   SNAND_MEMORG_4G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73E044VCB", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x2f),
 | 
						|
+		   SNAND_MEMORG_4G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73F044SNA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x24),
 | 
						|
+		   SNAND_MEMORG_8G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73F044VCA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x2d),
 | 
						|
+		   SNAND_MEMORG_8G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73E044SNE", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x0e),
 | 
						|
+		   SNAND_MEMORG_8G_4K_256,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73C044SNG", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x0c),
 | 
						|
+		   SNAND_MEMORG_1G_2K_120,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("EM73D044VCN", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x0f),
 | 
						|
+		   SNAND_MEMORG_2G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+
 | 
						|
+	SNAND_INFO("FM35Q1GA", SNAND_ID(SNAND_ID_DYMMY, 0xe5, 0x71),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+
 | 
						|
+	SNAND_INFO("PN26G01A", SNAND_ID(SNAND_ID_DYMMY, 0xa1, 0xe1),
 | 
						|
+		   SNAND_MEMORG_1G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("PN26G02A", SNAND_ID(SNAND_ID_DYMMY, 0xa1, 0xe2),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+
 | 
						|
+	SNAND_INFO("IS37SML01G1", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x21),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_x4,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+
 | 
						|
+	SNAND_INFO("ATO25D1GA", SNAND_ID(SNAND_ID_DYMMY, 0x9b, 0x12),
 | 
						|
+		   SNAND_MEMORG_1G_2K_64,
 | 
						|
+		   &snand_cap_read_from_cache_x4_only,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+
 | 
						|
+	SNAND_INFO("HYF1GQ4U", SNAND_ID(SNAND_ID_DYMMY, 0xc9, 0x51),
 | 
						|
+		   SNAND_MEMORG_1G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+	SNAND_INFO("HYF2GQ4U", SNAND_ID(SNAND_ID_DYMMY, 0xc9, 0x52),
 | 
						|
+		   SNAND_MEMORG_2G_2K_128,
 | 
						|
+		   &snand_cap_read_from_cache_quad_q2d,
 | 
						|
+		   &snand_cap_program_load_x4),
 | 
						|
+};
 | 
						|
+
 | 
						|
+static int mtk_snand_winbond_select_die(struct mtk_snand *snf, uint32_t dieidx)
 | 
						|
+{
 | 
						|
+	uint8_t op[2];
 | 
						|
+
 | 
						|
+	if (dieidx > 1) {
 | 
						|
+		snand_log_chip(snf->pdev, "Invalid die index %u\n", dieidx);
 | 
						|
+		return -EINVAL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	op[0] = SNAND_CMD_WINBOND_SELECT_DIE;
 | 
						|
+	op[1] = (uint8_t)dieidx;
 | 
						|
+
 | 
						|
+	return mtk_snand_mac_io(snf, op, sizeof(op), NULL, 0);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_micron_select_die(struct mtk_snand *snf, uint32_t dieidx)
 | 
						|
+{
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	if (dieidx > 1) {
 | 
						|
+		snand_log_chip(snf->pdev, "Invalid die index %u\n", dieidx);
 | 
						|
+		return -EINVAL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	ret = mtk_snand_set_feature(snf, SNAND_FEATURE_MICRON_DIE_ADDR,
 | 
						|
+				    SNAND_MICRON_DIE_SEL_1);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_chip(snf->pdev,
 | 
						|
+			       "Failed to set die selection feature\n");
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+const struct snand_flash_info *snand_flash_id_lookup(enum snand_id_type type,
 | 
						|
+						     const uint8_t *id)
 | 
						|
+{
 | 
						|
+	const struct snand_id *fid;
 | 
						|
+	uint32_t i;
 | 
						|
+
 | 
						|
+	for (i = 0; i < ARRAY_SIZE(snand_flash_ids); i++) {
 | 
						|
+		if (snand_flash_ids[i].id.type != type)
 | 
						|
+			continue;
 | 
						|
+
 | 
						|
+		fid = &snand_flash_ids[i].id;
 | 
						|
+		if (memcmp(fid->id, id, fid->len))
 | 
						|
+			continue;
 | 
						|
+
 | 
						|
+		return &snand_flash_ids[i];
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return NULL;
 | 
						|
+}
 | 
						|
--- /dev/null
 | 
						|
+++ b/drivers/mtd/mtk-snand/mtk-snand-mtd.c
 | 
						|
@@ -0,0 +1,535 @@
 | 
						|
+// SPDX-License-Identifier: GPL-2.0
 | 
						|
+/*
 | 
						|
+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
 | 
						|
+ *
 | 
						|
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
 | 
						|
+ */
 | 
						|
+
 | 
						|
+#include <common.h>
 | 
						|
+#include <dm.h>
 | 
						|
+#include <malloc.h>
 | 
						|
+#include <mapmem.h>
 | 
						|
+#include <linux/mtd/mtd.h>
 | 
						|
+#include <watchdog.h>
 | 
						|
+
 | 
						|
+#include "mtk-snand.h"
 | 
						|
+
 | 
						|
+struct mtk_snand_mtd {
 | 
						|
+	struct udevice *dev;
 | 
						|
+	struct mtk_snand *snf;
 | 
						|
+	struct mtk_snand_chip_info cinfo;
 | 
						|
+	uint8_t *page_cache;
 | 
						|
+};
 | 
						|
+
 | 
						|
+static const char snand_mtd_name_prefix[] = "spi-nand";
 | 
						|
+
 | 
						|
+static u32 snandidx;
 | 
						|
+
 | 
						|
+static inline struct mtk_snand_mtd *mtd_to_msm(struct mtd_info *mtd)
 | 
						|
+{
 | 
						|
+	return mtd->priv;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
 | 
						|
+{
 | 
						|
+	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);
 | 
						|
+	u64 start_addr, end_addr;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	/* Do not allow write past end of device */
 | 
						|
+	if ((instr->addr + instr->len) > mtd->size) {
 | 
						|
+		pr_debug("%s: attempt to erase beyond end of device\n",
 | 
						|
+			 __func__);
 | 
						|
+		return -EINVAL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	start_addr = instr->addr & (~mtd->erasesize_mask);
 | 
						|
+	end_addr = instr->addr + instr->len;
 | 
						|
+	if (end_addr & mtd->erasesize_mask) {
 | 
						|
+		end_addr = (end_addr + mtd->erasesize_mask) &
 | 
						|
+			   (~mtd->erasesize_mask);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	instr->state = MTD_ERASING;
 | 
						|
+
 | 
						|
+	while (start_addr < end_addr) {
 | 
						|
+		WATCHDOG_RESET();
 | 
						|
+
 | 
						|
+		if (mtk_snand_block_isbad(msm->snf, start_addr)) {
 | 
						|
+			if (!instr->scrub) {
 | 
						|
+				instr->fail_addr = start_addr;
 | 
						|
+				ret = -EIO;
 | 
						|
+				break;
 | 
						|
+			}
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		ret = mtk_snand_erase_block(msm->snf, start_addr);
 | 
						|
+		if (ret) {
 | 
						|
+			instr->fail_addr = start_addr;
 | 
						|
+			break;
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		start_addr += mtd->erasesize;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (!ret) {
 | 
						|
+		instr->state = MTD_ERASE_DONE;
 | 
						|
+	} else {
 | 
						|
+		instr->state = MTD_ERASE_FAILED;
 | 
						|
+		ret = -EIO;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_mtd_read_data(struct mtk_snand_mtd *msm, uint64_t addr,
 | 
						|
+				   struct mtd_oob_ops *ops)
 | 
						|
+{
 | 
						|
+	struct mtd_info *mtd = dev_get_uclass_priv(msm->dev);
 | 
						|
+	size_t len, ooblen, maxooblen, chklen;
 | 
						|
+	uint32_t col, ooboffs;
 | 
						|
+	uint8_t *datcache, *oobcache;
 | 
						|
+	bool ecc_failed = false, raw = ops->mode == MTD_OPS_RAW ? true : false;
 | 
						|
+	int ret, max_bitflips = 0;
 | 
						|
+
 | 
						|
+	col = addr & mtd->writesize_mask;
 | 
						|
+	addr &= ~mtd->writesize_mask;
 | 
						|
+	maxooblen = mtd_oobavail(mtd, ops);
 | 
						|
+	ooboffs = ops->ooboffs;
 | 
						|
+	ooblen = ops->ooblen;
 | 
						|
+	len = ops->len;
 | 
						|
+
 | 
						|
+	datcache = len ? msm->page_cache : NULL;
 | 
						|
+	oobcache = ooblen ? msm->page_cache + mtd->writesize : NULL;
 | 
						|
+
 | 
						|
+	ops->oobretlen = 0;
 | 
						|
+	ops->retlen = 0;
 | 
						|
+
 | 
						|
+	while (len || ooblen) {
 | 
						|
+		WATCHDOG_RESET();
 | 
						|
+
 | 
						|
+		if (ops->mode == MTD_OPS_AUTO_OOB)
 | 
						|
+			ret = mtk_snand_read_page_auto_oob(msm->snf, addr,
 | 
						|
+				datcache, oobcache, maxooblen, NULL, raw);
 | 
						|
+		else
 | 
						|
+			ret = mtk_snand_read_page(msm->snf, addr, datcache,
 | 
						|
+				oobcache, raw);
 | 
						|
+
 | 
						|
+		if (ret < 0 && ret != -EBADMSG)
 | 
						|
+			return ret;
 | 
						|
+
 | 
						|
+		if (ret == -EBADMSG) {
 | 
						|
+			mtd->ecc_stats.failed++;
 | 
						|
+			ecc_failed = true;
 | 
						|
+		} else {
 | 
						|
+			mtd->ecc_stats.corrected += ret;
 | 
						|
+			max_bitflips = max_t(int, ret, max_bitflips);
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		mtd->ecc_stats.corrected += ret;
 | 
						|
+		max_bitflips = max_t(int, ret, max_bitflips);
 | 
						|
+
 | 
						|
+		if (len) {
 | 
						|
+			/* Move data */
 | 
						|
+			chklen = mtd->writesize - col;
 | 
						|
+			if (chklen > len)
 | 
						|
+				chklen = len;
 | 
						|
+
 | 
						|
+			memcpy(ops->datbuf + ops->retlen, datcache + col,
 | 
						|
+			       chklen);
 | 
						|
+			len -= chklen;
 | 
						|
+			col = 0; /* (col + chklen) %  */
 | 
						|
+			ops->retlen += chklen;
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		if (ooblen) {
 | 
						|
+			/* Move oob */
 | 
						|
+			chklen = maxooblen - ooboffs;
 | 
						|
+			if (chklen > ooblen)
 | 
						|
+				chklen = ooblen;
 | 
						|
+
 | 
						|
+			memcpy(ops->oobbuf + ops->oobretlen, oobcache + ooboffs,
 | 
						|
+			       chklen);
 | 
						|
+			ooblen -= chklen;
 | 
						|
+			ooboffs = 0; /* (ooboffs + chklen) % maxooblen; */
 | 
						|
+			ops->oobretlen += chklen;
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		addr += mtd->writesize;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return ecc_failed ? -EBADMSG : max_bitflips;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_mtd_read_oob(struct mtd_info *mtd, loff_t from,
 | 
						|
+				  struct mtd_oob_ops *ops)
 | 
						|
+{
 | 
						|
+	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);
 | 
						|
+	uint32_t maxooblen;
 | 
						|
+
 | 
						|
+	if (!ops->oobbuf && !ops->datbuf) {
 | 
						|
+		if (ops->ooblen || ops->len)
 | 
						|
+			return -EINVAL;
 | 
						|
+
 | 
						|
+		return 0;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	switch (ops->mode) {
 | 
						|
+	case MTD_OPS_PLACE_OOB:
 | 
						|
+	case MTD_OPS_AUTO_OOB:
 | 
						|
+	case MTD_OPS_RAW:
 | 
						|
+		break;
 | 
						|
+	default:
 | 
						|
+		pr_debug("%s: unsupported oob mode: %u\n", __func__, ops->mode);
 | 
						|
+		return -EINVAL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	maxooblen = mtd_oobavail(mtd, ops);
 | 
						|
+
 | 
						|
+	/* Do not allow read past end of device */
 | 
						|
+	if (ops->datbuf && (from + ops->len) > mtd->size) {
 | 
						|
+		pr_debug("%s: attempt to read beyond end of device\n",
 | 
						|
+			 __func__);
 | 
						|
+		return -EINVAL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (unlikely(ops->ooboffs >= maxooblen)) {
 | 
						|
+		pr_debug("%s: attempt to start read outside oob\n",
 | 
						|
+			 __func__);
 | 
						|
+		return -EINVAL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (unlikely(from >= mtd->size ||
 | 
						|
+	    ops->ooboffs + ops->ooblen > ((mtd->size >> mtd->writesize_shift) -
 | 
						|
+	    (from >> mtd->writesize_shift)) * maxooblen)) {
 | 
						|
+		pr_debug("%s: attempt to read beyond end of device\n",
 | 
						|
+			 __func__);
 | 
						|
+		return -EINVAL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return mtk_snand_mtd_read_data(msm, from, ops);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_mtd_write_data(struct mtk_snand_mtd *msm, uint64_t addr,
 | 
						|
+				    struct mtd_oob_ops *ops)
 | 
						|
+{
 | 
						|
+	struct mtd_info *mtd = dev_get_uclass_priv(msm->dev);
 | 
						|
+	size_t len, ooblen, maxooblen, chklen, oobwrlen;
 | 
						|
+	uint32_t col, ooboffs;
 | 
						|
+	uint8_t *datcache, *oobcache;
 | 
						|
+	bool raw = ops->mode == MTD_OPS_RAW ? true : false;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	col = addr & mtd->writesize_mask;
 | 
						|
+	addr &= ~mtd->writesize_mask;
 | 
						|
+	maxooblen = mtd_oobavail(mtd, ops);
 | 
						|
+	ooboffs = ops->ooboffs;
 | 
						|
+	ooblen = ops->ooblen;
 | 
						|
+	len = ops->len;
 | 
						|
+
 | 
						|
+	datcache = len ? msm->page_cache : NULL;
 | 
						|
+	oobcache = ooblen ? msm->page_cache + mtd->writesize : NULL;
 | 
						|
+
 | 
						|
+	ops->oobretlen = 0;
 | 
						|
+	ops->retlen = 0;
 | 
						|
+
 | 
						|
+	while (len || ooblen) {
 | 
						|
+		WATCHDOG_RESET();
 | 
						|
+
 | 
						|
+		if (len) {
 | 
						|
+			/* Move data */
 | 
						|
+			chklen = mtd->writesize - col;
 | 
						|
+			if (chklen > len)
 | 
						|
+				chklen = len;
 | 
						|
+
 | 
						|
+			memset(datcache, 0xff, col);
 | 
						|
+			memcpy(datcache + col, ops->datbuf + ops->retlen,
 | 
						|
+			       chklen);
 | 
						|
+			memset(datcache + col + chklen, 0xff,
 | 
						|
+			       mtd->writesize - col - chklen);
 | 
						|
+			len -= chklen;
 | 
						|
+			col = 0; /* (col + chklen) %  */
 | 
						|
+			ops->retlen += chklen;
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		oobwrlen = 0;
 | 
						|
+		if (ooblen) {
 | 
						|
+			/* Move oob */
 | 
						|
+			chklen = maxooblen - ooboffs;
 | 
						|
+			if (chklen > ooblen)
 | 
						|
+				chklen = ooblen;
 | 
						|
+
 | 
						|
+			memset(oobcache, 0xff, ooboffs);
 | 
						|
+			memcpy(oobcache + ooboffs,
 | 
						|
+			       ops->oobbuf + ops->oobretlen, chklen);
 | 
						|
+			memset(oobcache + ooboffs + chklen, 0xff,
 | 
						|
+			       mtd->oobsize - ooboffs - chklen);
 | 
						|
+			oobwrlen = chklen + ooboffs;
 | 
						|
+			ooblen -= chklen;
 | 
						|
+			ooboffs = 0; /* (ooboffs + chklen) % maxooblen; */
 | 
						|
+			ops->oobretlen += chklen;
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		if (ops->mode == MTD_OPS_AUTO_OOB)
 | 
						|
+			ret = mtk_snand_write_page_auto_oob(msm->snf, addr,
 | 
						|
+				datcache, oobcache, oobwrlen, NULL, raw);
 | 
						|
+		else
 | 
						|
+			ret = mtk_snand_write_page(msm->snf, addr, datcache,
 | 
						|
+				oobcache, raw);
 | 
						|
+
 | 
						|
+		if (ret)
 | 
						|
+			return ret;
 | 
						|
+
 | 
						|
+		addr += mtd->writesize;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_mtd_write_oob(struct mtd_info *mtd, loff_t to,
 | 
						|
+				   struct mtd_oob_ops *ops)
 | 
						|
+{
 | 
						|
+	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);
 | 
						|
+	uint32_t maxooblen;
 | 
						|
+
 | 
						|
+	if (!ops->oobbuf && !ops->datbuf) {
 | 
						|
+		if (ops->ooblen || ops->len)
 | 
						|
+			return -EINVAL;
 | 
						|
+
 | 
						|
+		return 0;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	switch (ops->mode) {
 | 
						|
+	case MTD_OPS_PLACE_OOB:
 | 
						|
+	case MTD_OPS_AUTO_OOB:
 | 
						|
+	case MTD_OPS_RAW:
 | 
						|
+		break;
 | 
						|
+	default:
 | 
						|
+		pr_debug("%s: unsupported oob mode: %u\n", __func__, ops->mode);
 | 
						|
+		return -EINVAL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	maxooblen = mtd_oobavail(mtd, ops);
 | 
						|
+
 | 
						|
+	/* Do not allow write past end of device */
 | 
						|
+	if (ops->datbuf && (to + ops->len) > mtd->size) {
 | 
						|
+		pr_debug("%s: attempt to write beyond end of device\n",
 | 
						|
+			 __func__);
 | 
						|
+		return -EINVAL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (unlikely(ops->ooboffs >= maxooblen)) {
 | 
						|
+		pr_debug("%s: attempt to start write outside oob\n",
 | 
						|
+			 __func__);
 | 
						|
+		return -EINVAL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (unlikely(to >= mtd->size ||
 | 
						|
+	    ops->ooboffs + ops->ooblen > ((mtd->size >> mtd->writesize_shift) -
 | 
						|
+	    (to >> mtd->writesize_shift)) * maxooblen)) {
 | 
						|
+		pr_debug("%s: attempt to write beyond end of device\n",
 | 
						|
+			 __func__);
 | 
						|
+		return -EINVAL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return mtk_snand_mtd_write_data(msm, to, ops);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
 | 
						|
+			      size_t *retlen, u_char *buf)
 | 
						|
+{
 | 
						|
+	struct mtd_oob_ops ops = {
 | 
						|
+		.mode = MTD_OPS_PLACE_OOB,
 | 
						|
+		.datbuf = buf,
 | 
						|
+		.len = len,
 | 
						|
+	};
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_mtd_read_oob(mtd, from, &ops);
 | 
						|
+
 | 
						|
+	if (retlen)
 | 
						|
+		*retlen = ops.retlen;
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
 | 
						|
+			       size_t *retlen, const u_char *buf)
 | 
						|
+{
 | 
						|
+	struct mtd_oob_ops ops = {
 | 
						|
+		.mode = MTD_OPS_PLACE_OOB,
 | 
						|
+		.datbuf = (void *)buf,
 | 
						|
+		.len = len,
 | 
						|
+	};
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_mtd_write_oob(mtd, to, &ops);
 | 
						|
+
 | 
						|
+	if (retlen)
 | 
						|
+		*retlen = ops.retlen;
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs)
 | 
						|
+{
 | 
						|
+	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);
 | 
						|
+
 | 
						|
+	return mtk_snand_block_isbad(msm->snf, offs);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs)
 | 
						|
+{
 | 
						|
+	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);
 | 
						|
+
 | 
						|
+	return mtk_snand_block_markbad(msm->snf, offs);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_ooblayout_ecc(struct mtd_info *mtd, int section,
 | 
						|
+				   struct mtd_oob_region *oobecc)
 | 
						|
+{
 | 
						|
+	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);
 | 
						|
+
 | 
						|
+	if (section)
 | 
						|
+		return -ERANGE;
 | 
						|
+
 | 
						|
+	oobecc->offset = msm->cinfo.fdm_size * msm->cinfo.num_sectors;
 | 
						|
+	oobecc->length = mtd->oobsize - oobecc->offset;
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_ooblayout_free(struct mtd_info *mtd, int section,
 | 
						|
+				    struct mtd_oob_region *oobfree)
 | 
						|
+{
 | 
						|
+	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);
 | 
						|
+
 | 
						|
+	if (section >= msm->cinfo.num_sectors)
 | 
						|
+		return -ERANGE;
 | 
						|
+
 | 
						|
+	oobfree->length = msm->cinfo.fdm_size - 1;
 | 
						|
+	oobfree->offset = section * msm->cinfo.fdm_size + 1;
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static const struct mtd_ooblayout_ops mtk_snand_ooblayout = {
 | 
						|
+	.ecc = mtk_snand_ooblayout_ecc,
 | 
						|
+	.rfree = mtk_snand_ooblayout_free,
 | 
						|
+};
 | 
						|
+
 | 
						|
+static int mtk_snand_mtd_probe(struct udevice *dev)
 | 
						|
+{
 | 
						|
+	struct mtk_snand_mtd *msm = dev_get_priv(dev);
 | 
						|
+	struct mtd_info *mtd = dev_get_uclass_priv(dev);
 | 
						|
+	struct mtk_snand_platdata mtk_snand_pdata = {};
 | 
						|
+	size_t namelen;
 | 
						|
+	fdt_addr_t base;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	base = dev_read_addr_name(dev, "nfi");
 | 
						|
+	if (base == FDT_ADDR_T_NONE)
 | 
						|
+		return -EINVAL;
 | 
						|
+	mtk_snand_pdata.nfi_base = map_sysmem(base, 0);
 | 
						|
+
 | 
						|
+	base = dev_read_addr_name(dev, "ecc");
 | 
						|
+	if (base == FDT_ADDR_T_NONE)
 | 
						|
+		return -EINVAL;
 | 
						|
+	mtk_snand_pdata.ecc_base = map_sysmem(base, 0);
 | 
						|
+
 | 
						|
+	mtk_snand_pdata.soc = dev_get_driver_data(dev);
 | 
						|
+	mtk_snand_pdata.quad_spi = dev_read_bool(dev, "quad-spi");
 | 
						|
+
 | 
						|
+	ret = mtk_snand_init(NULL, &mtk_snand_pdata, &msm->snf);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	mtk_snand_get_chip_info(msm->snf, &msm->cinfo);
 | 
						|
+
 | 
						|
+	msm->page_cache = malloc(msm->cinfo.pagesize + msm->cinfo.sparesize);
 | 
						|
+	if (!msm->page_cache) {
 | 
						|
+		printf("%s: failed to allocate memory for page cache\n",
 | 
						|
+		       __func__);
 | 
						|
+		ret = -ENOMEM;
 | 
						|
+		goto errout1;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	namelen = sizeof(snand_mtd_name_prefix) + 12;
 | 
						|
+
 | 
						|
+	mtd->name = malloc(namelen);
 | 
						|
+	if (!mtd->name) {
 | 
						|
+		printf("%s: failed to allocate memory for MTD name\n",
 | 
						|
+		       __func__);
 | 
						|
+		ret = -ENOMEM;
 | 
						|
+		goto errout2;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	msm->dev = dev;
 | 
						|
+
 | 
						|
+	snprintf(mtd->name, namelen, "%s%u", snand_mtd_name_prefix, snandidx++);
 | 
						|
+
 | 
						|
+	mtd->priv = msm;
 | 
						|
+	mtd->dev = dev;
 | 
						|
+	mtd->type = MTD_NANDFLASH;
 | 
						|
+	mtd->flags = MTD_CAP_NANDFLASH;
 | 
						|
+
 | 
						|
+	mtd->size = msm->cinfo.chipsize;
 | 
						|
+	mtd->erasesize = msm->cinfo.blocksize;
 | 
						|
+	mtd->writesize = msm->cinfo.pagesize;
 | 
						|
+	mtd->writebufsize = mtd->writesize;
 | 
						|
+	mtd->oobsize = msm->cinfo.sparesize;
 | 
						|
+	mtd->oobavail = msm->cinfo.num_sectors * (msm->cinfo.fdm_size - 1);
 | 
						|
+
 | 
						|
+	mtd->ooblayout = &mtk_snand_ooblayout;
 | 
						|
+
 | 
						|
+	mtd->ecc_strength = msm->cinfo.ecc_strength;
 | 
						|
+	mtd->bitflip_threshold = (mtd->ecc_strength * 3) / 4;
 | 
						|
+	mtd->ecc_step_size = msm->cinfo.sector_size;
 | 
						|
+
 | 
						|
+	mtd->_read = mtk_snand_mtd_read;
 | 
						|
+	mtd->_write = mtk_snand_mtd_write;
 | 
						|
+	mtd->_erase = mtk_snand_mtd_erase;
 | 
						|
+	mtd->_read_oob = mtk_snand_mtd_read_oob;
 | 
						|
+	mtd->_write_oob = mtk_snand_mtd_write_oob;
 | 
						|
+	mtd->_block_isbad = mtk_snand_mtd_block_isbad;
 | 
						|
+	mtd->_block_markbad = mtk_snand_mtd_block_markbad;
 | 
						|
+
 | 
						|
+	ret = add_mtd_device(mtd);
 | 
						|
+	if (ret) {
 | 
						|
+		printf("%s: failed to add SPI-NAND MTD device\n", __func__);
 | 
						|
+		ret = -ENODEV;
 | 
						|
+		goto errout3;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	printf("SPI-NAND: %s (%lluMB)\n", msm->cinfo.model,
 | 
						|
+	       msm->cinfo.chipsize >> 20);
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+
 | 
						|
+errout3:
 | 
						|
+	free(mtd->name);
 | 
						|
+
 | 
						|
+errout2:
 | 
						|
+	free(msm->page_cache);
 | 
						|
+
 | 
						|
+errout1:
 | 
						|
+	mtk_snand_cleanup(msm->snf);
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static const struct udevice_id mtk_snand_ids[] = {
 | 
						|
+	{ .compatible = "mediatek,mt7622-snand", .data = SNAND_SOC_MT7622 },
 | 
						|
+	{ .compatible = "mediatek,mt7629-snand", .data = SNAND_SOC_MT7629 },
 | 
						|
+	{ .compatible = "mediatek,mt7981-snand", .data = SNAND_SOC_MT7981 },
 | 
						|
+	{ .compatible = "mediatek,mt7986-snand", .data = SNAND_SOC_MT7986 },
 | 
						|
+	{ /* sentinel */ },
 | 
						|
+};
 | 
						|
+
 | 
						|
+U_BOOT_DRIVER(spinand) = {
 | 
						|
+	.name = "mtk-snand",
 | 
						|
+	.id = UCLASS_MTD,
 | 
						|
+	.of_match = mtk_snand_ids,
 | 
						|
+	.priv_auto = sizeof(struct mtk_snand_mtd),
 | 
						|
+	.probe = mtk_snand_mtd_probe,
 | 
						|
+};
 | 
						|
--- /dev/null
 | 
						|
+++ b/drivers/mtd/mtk-snand/mtk-snand-os.c
 | 
						|
@@ -0,0 +1,39 @@
 | 
						|
+// SPDX-License-Identifier: GPL-2.0
 | 
						|
+/*
 | 
						|
+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
 | 
						|
+ *
 | 
						|
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
 | 
						|
+ */
 | 
						|
+
 | 
						|
+#include "mtk-snand-def.h"
 | 
						|
+
 | 
						|
+int mtk_snand_log(struct mtk_snand_plat_dev *pdev,
 | 
						|
+		  enum mtk_snand_log_category cat, const char *fmt, ...)
 | 
						|
+{
 | 
						|
+	const char *catname = "";
 | 
						|
+	va_list ap;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	switch (cat) {
 | 
						|
+	case SNAND_LOG_NFI:
 | 
						|
+		catname = "NFI: ";
 | 
						|
+		break;
 | 
						|
+	case SNAND_LOG_SNFI:
 | 
						|
+		catname = "SNFI: ";
 | 
						|
+		break;
 | 
						|
+	case SNAND_LOG_ECC:
 | 
						|
+		catname = "ECC: ";
 | 
						|
+		break;
 | 
						|
+	default:
 | 
						|
+		break;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	puts("SPI-NAND: ");
 | 
						|
+	puts(catname);
 | 
						|
+
 | 
						|
+	va_start(ap, fmt);
 | 
						|
+	ret = vprintf(fmt, ap);
 | 
						|
+	va_end(ap);
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
--- /dev/null
 | 
						|
+++ b/drivers/mtd/mtk-snand/mtk-snand-os.h
 | 
						|
@@ -0,0 +1,120 @@
 | 
						|
+/* SPDX-License-Identifier: GPL-2.0 */
 | 
						|
+/*
 | 
						|
+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
 | 
						|
+ *
 | 
						|
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
 | 
						|
+ */
 | 
						|
+
 | 
						|
+#ifndef _MTK_SNAND_OS_H_
 | 
						|
+#define _MTK_SNAND_OS_H_
 | 
						|
+
 | 
						|
+#include <common.h>
 | 
						|
+#include <cpu_func.h>
 | 
						|
+#include <errno.h>
 | 
						|
+#include <div64.h>
 | 
						|
+#include <malloc.h>
 | 
						|
+#include <stdbool.h>
 | 
						|
+#include <stdarg.h>
 | 
						|
+#include <linux/types.h>
 | 
						|
+#include <asm/io.h>
 | 
						|
+#include <linux/bitops.h>
 | 
						|
+#include <linux/sizes.h>
 | 
						|
+#include <linux/iopoll.h>
 | 
						|
+
 | 
						|
+#ifndef ARCH_DMA_MINALIGN
 | 
						|
+#define ARCH_DMA_MINALIGN		64
 | 
						|
+#endif
 | 
						|
+
 | 
						|
+struct mtk_snand_plat_dev {
 | 
						|
+	ulong unused;
 | 
						|
+};
 | 
						|
+
 | 
						|
+/* Polling helpers */
 | 
						|
+#define read16_poll_timeout(addr, val, cond, sleep_us, timeout_us) \
 | 
						|
+	readw_poll_timeout((addr), (val), (cond), (timeout_us))
 | 
						|
+
 | 
						|
+#define read32_poll_timeout(addr, val, cond, sleep_us, timeout_us) \
 | 
						|
+	readl_poll_timeout((addr), (val), (cond), (timeout_us))
 | 
						|
+
 | 
						|
+/* Timer helpers */
 | 
						|
+typedef uint64_t mtk_snand_time_t;
 | 
						|
+
 | 
						|
+static inline mtk_snand_time_t timer_get_ticks(void)
 | 
						|
+{
 | 
						|
+	return get_ticks();
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline mtk_snand_time_t timer_time_to_tick(uint32_t timeout_us)
 | 
						|
+{
 | 
						|
+	return usec_to_tick(timeout_us);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline bool timer_is_timeout(mtk_snand_time_t start_tick,
 | 
						|
+				    mtk_snand_time_t timeout_tick)
 | 
						|
+{
 | 
						|
+	return get_ticks() - start_tick > timeout_tick;
 | 
						|
+}
 | 
						|
+
 | 
						|
+/* Memory helpers */
 | 
						|
+static inline void *generic_mem_alloc(struct mtk_snand_plat_dev *pdev,
 | 
						|
+				      size_t size)
 | 
						|
+{
 | 
						|
+	return calloc(1, size);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline void generic_mem_free(struct mtk_snand_plat_dev *pdev, void *ptr)
 | 
						|
+{
 | 
						|
+	free(ptr);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline void *dma_mem_alloc(struct mtk_snand_plat_dev *pdev, size_t size)
 | 
						|
+{
 | 
						|
+	return memalign(ARCH_DMA_MINALIGN, size);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline void dma_mem_free(struct mtk_snand_plat_dev *pdev, void *ptr)
 | 
						|
+{
 | 
						|
+	free(ptr);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline int dma_mem_map(struct mtk_snand_plat_dev *pdev, void *vaddr,
 | 
						|
+			      uintptr_t *dma_addr, size_t size, bool to_device)
 | 
						|
+{
 | 
						|
+	size_t cachelen = roundup(size, ARCH_DMA_MINALIGN);
 | 
						|
+	uintptr_t endaddr = (uintptr_t)vaddr + cachelen;
 | 
						|
+
 | 
						|
+	if (to_device)
 | 
						|
+		flush_dcache_range((uintptr_t)vaddr, endaddr);
 | 
						|
+	else
 | 
						|
+		invalidate_dcache_range((uintptr_t)vaddr, endaddr);
 | 
						|
+
 | 
						|
+	*dma_addr = (uintptr_t)vaddr;
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline void dma_mem_unmap(struct mtk_snand_plat_dev *pdev,
 | 
						|
+				 uintptr_t dma_addr, size_t size,
 | 
						|
+				 bool to_device)
 | 
						|
+{
 | 
						|
+}
 | 
						|
+
 | 
						|
+/* Interrupt helpers */
 | 
						|
+static inline void irq_completion_done(struct mtk_snand_plat_dev *pdev)
 | 
						|
+{
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline void irq_completion_init(struct mtk_snand_plat_dev *pdev)
 | 
						|
+{
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline int irq_completion_wait(struct mtk_snand_plat_dev *pdev,
 | 
						|
+				      void __iomem *reg, uint32_t bit,
 | 
						|
+				      uint32_t timeout_us)
 | 
						|
+{
 | 
						|
+	uint32_t val;
 | 
						|
+
 | 
						|
+	return read32_poll_timeout(reg, val, val & bit, 0, timeout_us);
 | 
						|
+}
 | 
						|
+
 | 
						|
+#endif /* _MTK_SNAND_OS_H_ */
 | 
						|
--- /dev/null
 | 
						|
+++ b/drivers/mtd/mtk-snand/mtk-snand.c
 | 
						|
@@ -0,0 +1,1933 @@
 | 
						|
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
 | 
						|
+/*
 | 
						|
+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
 | 
						|
+ *
 | 
						|
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
 | 
						|
+ */
 | 
						|
+
 | 
						|
+#include "mtk-snand-def.h"
 | 
						|
+
 | 
						|
+/* NFI registers */
 | 
						|
+#define NFI_CNFG			0x000
 | 
						|
+#define CNFG_OP_MODE_S			12
 | 
						|
+#define   CNFG_OP_MODE_CUST		6
 | 
						|
+#define   CNFG_OP_MODE_PROGRAM		3
 | 
						|
+#define CNFG_AUTO_FMT_EN		BIT(9)
 | 
						|
+#define CNFG_HW_ECC_EN			BIT(8)
 | 
						|
+#define CNFG_DMA_BURST_EN		BIT(2)
 | 
						|
+#define CNFG_READ_MODE			BIT(1)
 | 
						|
+#define CNFG_DMA_MODE			BIT(0)
 | 
						|
+
 | 
						|
+#define NFI_PAGEFMT			0x0004
 | 
						|
+#define NFI_SPARE_SIZE_LS_S		16
 | 
						|
+#define NFI_FDM_ECC_NUM_S		12
 | 
						|
+#define NFI_FDM_NUM_S			8
 | 
						|
+#define NFI_SPARE_SIZE_S		4
 | 
						|
+#define NFI_SEC_SEL_512			BIT(2)
 | 
						|
+#define NFI_PAGE_SIZE_S			0
 | 
						|
+#define   NFI_PAGE_SIZE_512_2K		0
 | 
						|
+#define   NFI_PAGE_SIZE_2K_4K		1
 | 
						|
+#define   NFI_PAGE_SIZE_4K_8K		2
 | 
						|
+#define   NFI_PAGE_SIZE_8K_16K		3
 | 
						|
+
 | 
						|
+#define NFI_CON				0x008
 | 
						|
+#define CON_SEC_NUM_S			12
 | 
						|
+#define CON_BWR				BIT(9)
 | 
						|
+#define CON_BRD				BIT(8)
 | 
						|
+#define CON_NFI_RST			BIT(1)
 | 
						|
+#define CON_FIFO_FLUSH			BIT(0)
 | 
						|
+
 | 
						|
+#define NFI_INTR_EN			0x010
 | 
						|
+#define NFI_INTR_STA			0x014
 | 
						|
+#define NFI_IRQ_INTR_EN			BIT(31)
 | 
						|
+#define NFI_IRQ_CUS_READ		BIT(8)
 | 
						|
+#define NFI_IRQ_CUS_PG			BIT(7)
 | 
						|
+
 | 
						|
+#define NFI_CMD				0x020
 | 
						|
+
 | 
						|
+#define NFI_STRDATA			0x040
 | 
						|
+#define STR_DATA			BIT(0)
 | 
						|
+
 | 
						|
+#define NFI_STA				0x060
 | 
						|
+#define NFI_NAND_FSM			GENMASK(28, 24)
 | 
						|
+#define NFI_FSM				GENMASK(19, 16)
 | 
						|
+#define READ_EMPTY			BIT(12)
 | 
						|
+
 | 
						|
+#define NFI_FIFOSTA			0x064
 | 
						|
+#define FIFO_WR_REMAIN_S		8
 | 
						|
+#define FIFO_RD_REMAIN_S		0
 | 
						|
+
 | 
						|
+#define NFI_ADDRCNTR			0x070
 | 
						|
+#define SEC_CNTR			GENMASK(16, 12)
 | 
						|
+#define SEC_CNTR_S			12
 | 
						|
+#define NFI_SEC_CNTR(val)               (((val) & SEC_CNTR) >> SEC_CNTR_S)
 | 
						|
+
 | 
						|
+#define NFI_STRADDR			0x080
 | 
						|
+
 | 
						|
+#define NFI_BYTELEN			0x084
 | 
						|
+#define BUS_SEC_CNTR(val)		(((val) & SEC_CNTR) >> SEC_CNTR_S)
 | 
						|
+
 | 
						|
+#define NFI_FDM0L			0x0a0
 | 
						|
+#define NFI_FDM0M			0x0a4
 | 
						|
+#define NFI_FDML(n)			(NFI_FDM0L + (n) * 8)
 | 
						|
+#define NFI_FDMM(n)			(NFI_FDM0M + (n) * 8)
 | 
						|
+
 | 
						|
+#define NFI_DEBUG_CON1			0x220
 | 
						|
+#define WBUF_EN				BIT(2)
 | 
						|
+
 | 
						|
+#define NFI_MASTERSTA			0x224
 | 
						|
+#define MAS_ADDR			GENMASK(11, 9)
 | 
						|
+#define MAS_RD				GENMASK(8, 6)
 | 
						|
+#define MAS_WR				GENMASK(5, 3)
 | 
						|
+#define MAS_RDDLY			GENMASK(2, 0)
 | 
						|
+#define NFI_MASTERSTA_MASK_7622		(MAS_ADDR | MAS_RD | MAS_WR | MAS_RDDLY)
 | 
						|
+#define AHB_BUS_BUSY			BIT(1)
 | 
						|
+#define BUS_BUSY			BIT(0)
 | 
						|
+#define NFI_MASTERSTA_MASK_7981		(AHB_BUS_BUSY | BUS_BUSY)
 | 
						|
+#define NFI_MASTERSTA_MASK_7986		(AHB_BUS_BUSY | BUS_BUSY)
 | 
						|
+
 | 
						|
+/* SNFI registers */
 | 
						|
+#define SNF_MAC_CTL			0x500
 | 
						|
+#define MAC_XIO_SEL			BIT(4)
 | 
						|
+#define SF_MAC_EN			BIT(3)
 | 
						|
+#define SF_TRIG				BIT(2)
 | 
						|
+#define WIP_READY			BIT(1)
 | 
						|
+#define WIP				BIT(0)
 | 
						|
+
 | 
						|
+#define SNF_MAC_OUTL			0x504
 | 
						|
+#define SNF_MAC_INL			0x508
 | 
						|
+
 | 
						|
+#define SNF_RD_CTL2			0x510
 | 
						|
+#define DATA_READ_DUMMY_S		8
 | 
						|
+#define DATA_READ_CMD_S			0
 | 
						|
+
 | 
						|
+#define SNF_RD_CTL3			0x514
 | 
						|
+
 | 
						|
+#define SNF_PG_CTL1			0x524
 | 
						|
+#define PG_LOAD_CMD_S			8
 | 
						|
+
 | 
						|
+#define SNF_PG_CTL2			0x528
 | 
						|
+
 | 
						|
+#define SNF_MISC_CTL			0x538
 | 
						|
+#define SW_RST				BIT(28)
 | 
						|
+#define FIFO_RD_LTC_S			25
 | 
						|
+#define PG_LOAD_X4_EN			BIT(20)
 | 
						|
+#define DATA_READ_MODE_S		16
 | 
						|
+#define DATA_READ_MODE			GENMASK(18, 16)
 | 
						|
+#define   DATA_READ_MODE_X1		0
 | 
						|
+#define   DATA_READ_MODE_X2		1
 | 
						|
+#define   DATA_READ_MODE_X4		2
 | 
						|
+#define   DATA_READ_MODE_DUAL		5
 | 
						|
+#define   DATA_READ_MODE_QUAD		6
 | 
						|
+#define LATCH_LAT_S			8
 | 
						|
+#define LATCH_LAT			GENMASK(9, 8)
 | 
						|
+#define PG_LOAD_CUSTOM_EN		BIT(7)
 | 
						|
+#define DATARD_CUSTOM_EN		BIT(6)
 | 
						|
+#define CS_DESELECT_CYC_S		0
 | 
						|
+
 | 
						|
+#define SNF_MISC_CTL2			0x53c
 | 
						|
+#define PROGRAM_LOAD_BYTE_NUM_S		16
 | 
						|
+#define READ_DATA_BYTE_NUM_S		11
 | 
						|
+
 | 
						|
+#define SNF_DLY_CTL3			0x548
 | 
						|
+#define SFCK_SAM_DLY_S			0
 | 
						|
+
 | 
						|
+#define SNF_STA_CTL1			0x550
 | 
						|
+#define CUS_PG_DONE			BIT(28)
 | 
						|
+#define CUS_READ_DONE			BIT(27)
 | 
						|
+#define SPI_STATE_S			0
 | 
						|
+#define SPI_STATE			GENMASK(3, 0)
 | 
						|
+
 | 
						|
+#define SNF_CFG				0x55c
 | 
						|
+#define SPI_MODE			BIT(0)
 | 
						|
+
 | 
						|
+#define SNF_GPRAM			0x800
 | 
						|
+#define SNF_GPRAM_SIZE			0xa0
 | 
						|
+
 | 
						|
+#define SNFI_POLL_INTERVAL		1000000
 | 
						|
+
 | 
						|
+static const uint8_t mt7622_spare_sizes[] = { 16, 26, 27, 28 };
 | 
						|
+
 | 
						|
+static const uint8_t mt7981_spare_sizes[] = {
 | 
						|
+	16, 26, 27, 28, 32, 36, 40, 44, 48, 49, 50, 51, 52, 62, 61, 63, 64,
 | 
						|
+	67, 74
 | 
						|
+};
 | 
						|
+
 | 
						|
+static const uint8_t mt7986_spare_sizes[] = {
 | 
						|
+	16, 26, 27, 28, 32, 36, 40, 44, 48, 49, 50, 51, 52, 62, 61, 63, 64,
 | 
						|
+	67, 74
 | 
						|
+};
 | 
						|
+
 | 
						|
+static const struct mtk_snand_soc_data mtk_snand_socs[__SNAND_SOC_MAX] = {
 | 
						|
+	[SNAND_SOC_MT7622] = {
 | 
						|
+		.sector_size = 512,
 | 
						|
+		.max_sectors = 8,
 | 
						|
+		.fdm_size = 8,
 | 
						|
+		.fdm_ecc_size = 1,
 | 
						|
+		.fifo_size = 32,
 | 
						|
+		.bbm_swap = false,
 | 
						|
+		.empty_page_check = false,
 | 
						|
+		.mastersta_mask = NFI_MASTERSTA_MASK_7622,
 | 
						|
+		.spare_sizes = mt7622_spare_sizes,
 | 
						|
+		.num_spare_size = ARRAY_SIZE(mt7622_spare_sizes),
 | 
						|
+		.latch_lat = 0,
 | 
						|
+		.sample_delay = 40
 | 
						|
+	},
 | 
						|
+	[SNAND_SOC_MT7629] = {
 | 
						|
+		.sector_size = 512,
 | 
						|
+		.max_sectors = 8,
 | 
						|
+		.fdm_size = 8,
 | 
						|
+		.fdm_ecc_size = 1,
 | 
						|
+		.fifo_size = 32,
 | 
						|
+		.bbm_swap = true,
 | 
						|
+		.empty_page_check = false,
 | 
						|
+		.mastersta_mask = NFI_MASTERSTA_MASK_7622,
 | 
						|
+		.spare_sizes = mt7622_spare_sizes,
 | 
						|
+		.num_spare_size = ARRAY_SIZE(mt7622_spare_sizes),
 | 
						|
+		.latch_lat = 0,
 | 
						|
+		.sample_delay = 40
 | 
						|
+	},
 | 
						|
+	[SNAND_SOC_MT7981] = {
 | 
						|
+		.sector_size = 1024,
 | 
						|
+		.max_sectors = 16,
 | 
						|
+		.fdm_size = 8,
 | 
						|
+		.fdm_ecc_size = 1,
 | 
						|
+		.fifo_size = 64,
 | 
						|
+		.bbm_swap = true,
 | 
						|
+		.empty_page_check = true,
 | 
						|
+		.mastersta_mask = NFI_MASTERSTA_MASK_7981,
 | 
						|
+		.spare_sizes = mt7981_spare_sizes,
 | 
						|
+		.num_spare_size = ARRAY_SIZE(mt7981_spare_sizes),
 | 
						|
+		.latch_lat = 0,
 | 
						|
+		.sample_delay = 40
 | 
						|
+	},
 | 
						|
+	[SNAND_SOC_MT7986] = {
 | 
						|
+		.sector_size = 1024,
 | 
						|
+		.max_sectors = 16,
 | 
						|
+		.fdm_size = 8,
 | 
						|
+		.fdm_ecc_size = 1,
 | 
						|
+		.fifo_size = 64,
 | 
						|
+		.bbm_swap = true,
 | 
						|
+		.empty_page_check = true,
 | 
						|
+		.mastersta_mask = NFI_MASTERSTA_MASK_7986,
 | 
						|
+		.spare_sizes = mt7986_spare_sizes,
 | 
						|
+		.num_spare_size = ARRAY_SIZE(mt7986_spare_sizes),
 | 
						|
+		.latch_lat = 0,
 | 
						|
+		.sample_delay = 40
 | 
						|
+	},
 | 
						|
+};
 | 
						|
+
 | 
						|
+static inline uint32_t nfi_read32(struct mtk_snand *snf, uint32_t reg)
 | 
						|
+{
 | 
						|
+	return readl(snf->nfi_base + reg);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline void nfi_write32(struct mtk_snand *snf, uint32_t reg,
 | 
						|
+			       uint32_t val)
 | 
						|
+{
 | 
						|
+	writel(val, snf->nfi_base + reg);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline void nfi_write16(struct mtk_snand *snf, uint32_t reg,
 | 
						|
+			       uint16_t val)
 | 
						|
+{
 | 
						|
+	writew(val, snf->nfi_base + reg);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline void nfi_rmw32(struct mtk_snand *snf, uint32_t reg, uint32_t clr,
 | 
						|
+			     uint32_t set)
 | 
						|
+{
 | 
						|
+	uint32_t val;
 | 
						|
+
 | 
						|
+	val = readl(snf->nfi_base + reg);
 | 
						|
+	val &= ~clr;
 | 
						|
+	val |= set;
 | 
						|
+	writel(val, snf->nfi_base + reg);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void nfi_write_data(struct mtk_snand *snf, uint32_t reg,
 | 
						|
+			   const uint8_t *data, uint32_t len)
 | 
						|
+{
 | 
						|
+	uint32_t i, val = 0, es = sizeof(uint32_t);
 | 
						|
+
 | 
						|
+	for (i = reg; i < reg + len; i++) {
 | 
						|
+		val |= ((uint32_t)*data++) << (8 * (i % es));
 | 
						|
+
 | 
						|
+		if (i % es == es - 1 || i == reg + len - 1) {
 | 
						|
+			nfi_write32(snf, i & ~(es - 1), val);
 | 
						|
+			val = 0;
 | 
						|
+		}
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void nfi_read_data(struct mtk_snand *snf, uint32_t reg, uint8_t *data,
 | 
						|
+			  uint32_t len)
 | 
						|
+{
 | 
						|
+	uint32_t i, val = 0, es = sizeof(uint32_t);
 | 
						|
+
 | 
						|
+	for (i = reg; i < reg + len; i++) {
 | 
						|
+		if (i == reg || i % es == 0)
 | 
						|
+			val = nfi_read32(snf, i & ~(es - 1));
 | 
						|
+
 | 
						|
+		*data++ = (uint8_t)(val >> (8 * (i % es)));
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline void do_bm_swap(uint8_t *bm1, uint8_t *bm2)
 | 
						|
+{
 | 
						|
+	uint8_t tmp = *bm1;
 | 
						|
+	*bm1 = *bm2;
 | 
						|
+	*bm2 = tmp;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void mtk_snand_bm_swap_raw(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	uint32_t fdm_bbm_pos;
 | 
						|
+
 | 
						|
+	if (!snf->nfi_soc->bbm_swap || snf->ecc_steps == 1)
 | 
						|
+		return;
 | 
						|
+
 | 
						|
+	fdm_bbm_pos = (snf->ecc_steps - 1) * snf->raw_sector_size +
 | 
						|
+		      snf->nfi_soc->sector_size;
 | 
						|
+	do_bm_swap(&snf->page_cache[fdm_bbm_pos],
 | 
						|
+		   &snf->page_cache[snf->writesize]);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void mtk_snand_bm_swap(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	uint32_t buf_bbm_pos, fdm_bbm_pos;
 | 
						|
+
 | 
						|
+	if (!snf->nfi_soc->bbm_swap || snf->ecc_steps == 1)
 | 
						|
+		return;
 | 
						|
+
 | 
						|
+	buf_bbm_pos = snf->writesize -
 | 
						|
+		      (snf->ecc_steps - 1) * snf->spare_per_sector;
 | 
						|
+	fdm_bbm_pos = snf->writesize +
 | 
						|
+		      (snf->ecc_steps - 1) * snf->nfi_soc->fdm_size;
 | 
						|
+	do_bm_swap(&snf->page_cache[fdm_bbm_pos],
 | 
						|
+		   &snf->page_cache[buf_bbm_pos]);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void mtk_snand_fdm_bm_swap_raw(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	uint32_t fdm_bbm_pos1, fdm_bbm_pos2;
 | 
						|
+
 | 
						|
+	if (!snf->nfi_soc->bbm_swap || snf->ecc_steps == 1)
 | 
						|
+		return;
 | 
						|
+
 | 
						|
+	fdm_bbm_pos1 = snf->nfi_soc->sector_size;
 | 
						|
+	fdm_bbm_pos2 = (snf->ecc_steps - 1) * snf->raw_sector_size +
 | 
						|
+		       snf->nfi_soc->sector_size;
 | 
						|
+	do_bm_swap(&snf->page_cache[fdm_bbm_pos1],
 | 
						|
+		   &snf->page_cache[fdm_bbm_pos2]);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void mtk_snand_fdm_bm_swap(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	uint32_t fdm_bbm_pos1, fdm_bbm_pos2;
 | 
						|
+
 | 
						|
+	if (!snf->nfi_soc->bbm_swap || snf->ecc_steps == 1)
 | 
						|
+		return;
 | 
						|
+
 | 
						|
+	fdm_bbm_pos1 = snf->writesize;
 | 
						|
+	fdm_bbm_pos2 = snf->writesize +
 | 
						|
+		       (snf->ecc_steps - 1) * snf->nfi_soc->fdm_size;
 | 
						|
+	do_bm_swap(&snf->page_cache[fdm_bbm_pos1],
 | 
						|
+		   &snf->page_cache[fdm_bbm_pos2]);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_nfi_reset(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	uint32_t val, fifo_mask;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	nfi_write32(snf, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST);
 | 
						|
+
 | 
						|
+	ret = read16_poll_timeout(snf->nfi_base + NFI_MASTERSTA, val,
 | 
						|
+				  !(val & snf->nfi_soc->mastersta_mask), 0,
 | 
						|
+				  SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_nfi(snf->pdev,
 | 
						|
+			      "NFI master is still busy after reset\n");
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	ret = read32_poll_timeout(snf->nfi_base + NFI_STA, val,
 | 
						|
+				  !(val & (NFI_FSM | NFI_NAND_FSM)), 0,
 | 
						|
+				  SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_nfi(snf->pdev, "Failed to reset NFI\n");
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	fifo_mask = ((snf->nfi_soc->fifo_size - 1) << FIFO_RD_REMAIN_S) |
 | 
						|
+		    ((snf->nfi_soc->fifo_size - 1) << FIFO_WR_REMAIN_S);
 | 
						|
+	ret = read16_poll_timeout(snf->nfi_base + NFI_FIFOSTA, val,
 | 
						|
+				  !(val & fifo_mask), 0, SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_nfi(snf->pdev, "NFI FIFOs are not empty\n");
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_mac_reset(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	int ret;
 | 
						|
+	uint32_t val;
 | 
						|
+
 | 
						|
+	nfi_rmw32(snf, SNF_MISC_CTL, 0, SW_RST);
 | 
						|
+
 | 
						|
+	ret = read32_poll_timeout(snf->nfi_base + SNF_STA_CTL1, val,
 | 
						|
+				  !(val & SPI_STATE), 0, SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret)
 | 
						|
+		snand_log_snfi(snf->pdev, "Failed to reset SNFI MAC\n");
 | 
						|
+
 | 
						|
+	nfi_write32(snf, SNF_MISC_CTL, (2 << FIFO_RD_LTC_S) |
 | 
						|
+		    (10 << CS_DESELECT_CYC_S) | (snf->nfi_soc->latch_lat << LATCH_LAT_S));
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_mac_trigger(struct mtk_snand *snf, uint32_t outlen,
 | 
						|
+				 uint32_t inlen)
 | 
						|
+{
 | 
						|
+	int ret;
 | 
						|
+	uint32_t val;
 | 
						|
+
 | 
						|
+	nfi_write32(snf, SNF_MAC_CTL, SF_MAC_EN);
 | 
						|
+	nfi_write32(snf, SNF_MAC_OUTL, outlen);
 | 
						|
+	nfi_write32(snf, SNF_MAC_INL, inlen);
 | 
						|
+
 | 
						|
+	nfi_write32(snf, SNF_MAC_CTL, SF_MAC_EN | SF_TRIG);
 | 
						|
+
 | 
						|
+	ret = read32_poll_timeout(snf->nfi_base + SNF_MAC_CTL, val,
 | 
						|
+				  val & WIP_READY, 0, SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_snfi(snf->pdev, "Timed out waiting for WIP_READY\n");
 | 
						|
+		goto cleanup;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	ret = read32_poll_timeout(snf->nfi_base + SNF_MAC_CTL, val,
 | 
						|
+				  !(val & WIP), 0, SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_snfi(snf->pdev,
 | 
						|
+			       "Timed out waiting for WIP cleared\n");
 | 
						|
+	}
 | 
						|
+
 | 
						|
+cleanup:
 | 
						|
+	nfi_write32(snf, SNF_MAC_CTL, 0);
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_mac_io(struct mtk_snand *snf, const uint8_t *out, uint32_t outlen,
 | 
						|
+		     uint8_t *in, uint32_t inlen)
 | 
						|
+{
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	if (outlen + inlen > SNF_GPRAM_SIZE)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	mtk_snand_mac_reset(snf);
 | 
						|
+
 | 
						|
+	nfi_write_data(snf, SNF_GPRAM, out, outlen);
 | 
						|
+
 | 
						|
+	ret = mtk_snand_mac_trigger(snf, outlen, inlen);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	if (!inlen)
 | 
						|
+		return 0;
 | 
						|
+
 | 
						|
+	nfi_read_data(snf, SNF_GPRAM + outlen, in, inlen);
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_get_feature(struct mtk_snand *snf, uint32_t addr)
 | 
						|
+{
 | 
						|
+	uint8_t op[2], val;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	op[0] = SNAND_CMD_GET_FEATURE;
 | 
						|
+	op[1] = (uint8_t)addr;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_mac_io(snf, op, sizeof(op), &val, 1);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	return val;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_set_feature(struct mtk_snand *snf, uint32_t addr, uint32_t val)
 | 
						|
+{
 | 
						|
+	uint8_t op[3];
 | 
						|
+
 | 
						|
+	op[0] = SNAND_CMD_SET_FEATURE;
 | 
						|
+	op[1] = (uint8_t)addr;
 | 
						|
+	op[2] = (uint8_t)val;
 | 
						|
+
 | 
						|
+	return mtk_snand_mac_io(snf, op, sizeof(op), NULL, 0);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_poll_status(struct mtk_snand *snf, uint32_t wait_us)
 | 
						|
+{
 | 
						|
+	int val;
 | 
						|
+	mtk_snand_time_t time_start, tmo;
 | 
						|
+
 | 
						|
+	time_start = timer_get_ticks();
 | 
						|
+	tmo = timer_time_to_tick(wait_us);
 | 
						|
+
 | 
						|
+	do {
 | 
						|
+		val = mtk_snand_get_feature(snf, SNAND_FEATURE_STATUS_ADDR);
 | 
						|
+		if (!(val & SNAND_STATUS_OIP))
 | 
						|
+			return val & (SNAND_STATUS_ERASE_FAIL |
 | 
						|
+				      SNAND_STATUS_PROGRAM_FAIL);
 | 
						|
+	} while (!timer_is_timeout(time_start, tmo));
 | 
						|
+
 | 
						|
+	return -ETIMEDOUT;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_chip_reset(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	uint8_t op = SNAND_CMD_RESET;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_mac_io(snf, &op, 1, NULL, 0);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_poll_status(snf, SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret < 0)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_config_feature(struct mtk_snand *snf, uint8_t clr,
 | 
						|
+				    uint8_t set)
 | 
						|
+{
 | 
						|
+	int val, newval;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	val = mtk_snand_get_feature(snf, SNAND_FEATURE_CONFIG_ADDR);
 | 
						|
+	if (val < 0) {
 | 
						|
+		snand_log_chip(snf->pdev,
 | 
						|
+			       "Failed to get configuration feature\n");
 | 
						|
+		return val;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	newval = (val & (~clr)) | set;
 | 
						|
+
 | 
						|
+	if (newval == val)
 | 
						|
+		return 0;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_set_feature(snf, SNAND_FEATURE_CONFIG_ADDR,
 | 
						|
+				    (uint8_t)newval);
 | 
						|
+	if (val < 0) {
 | 
						|
+		snand_log_chip(snf->pdev,
 | 
						|
+			       "Failed to set configuration feature\n");
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	val = mtk_snand_get_feature(snf, SNAND_FEATURE_CONFIG_ADDR);
 | 
						|
+	if (val < 0) {
 | 
						|
+		snand_log_chip(snf->pdev,
 | 
						|
+			       "Failed to get configuration feature\n");
 | 
						|
+		return val;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (newval != val)
 | 
						|
+		return -ENOTSUPP;
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_ondie_ecc_control(struct mtk_snand *snf, bool enable)
 | 
						|
+{
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	if (enable)
 | 
						|
+		ret = mtk_snand_config_feature(snf, 0, SNAND_FEATURE_ECC_EN);
 | 
						|
+	else
 | 
						|
+		ret = mtk_snand_config_feature(snf, SNAND_FEATURE_ECC_EN, 0);
 | 
						|
+
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_chip(snf->pdev, "Failed to %s On-Die ECC engine\n",
 | 
						|
+			       enable ? "enable" : "disable");
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_qspi_control(struct mtk_snand *snf, bool enable)
 | 
						|
+{
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	if (enable) {
 | 
						|
+		ret = mtk_snand_config_feature(snf, 0,
 | 
						|
+					       SNAND_FEATURE_QUAD_ENABLE);
 | 
						|
+	} else {
 | 
						|
+		ret = mtk_snand_config_feature(snf,
 | 
						|
+					       SNAND_FEATURE_QUAD_ENABLE, 0);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_chip(snf->pdev, "Failed to %s quad spi\n",
 | 
						|
+			       enable ? "enable" : "disable");
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_unlock(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_set_feature(snf, SNAND_FEATURE_PROTECT_ADDR, 0);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_chip(snf->pdev, "Failed to set protection feature\n");
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_write_enable(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	uint8_t op = SNAND_CMD_WRITE_ENABLE;
 | 
						|
+	int ret, val;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_mac_io(snf, &op, 1, NULL, 0);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	val = mtk_snand_get_feature(snf, SNAND_FEATURE_STATUS_ADDR);
 | 
						|
+	if (val < 0)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	if (val & SNAND_STATUS_WEL)
 | 
						|
+		return 0;
 | 
						|
+
 | 
						|
+	snand_log_chip(snf->pdev, "Failed to send write-enable command\n");
 | 
						|
+
 | 
						|
+	return -ENOTSUPP;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_select_die(struct mtk_snand *snf, uint32_t dieidx)
 | 
						|
+{
 | 
						|
+	if (!snf->select_die)
 | 
						|
+		return 0;
 | 
						|
+
 | 
						|
+	return snf->select_die(snf, dieidx);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static uint64_t mtk_snand_select_die_address(struct mtk_snand *snf,
 | 
						|
+					     uint64_t addr)
 | 
						|
+{
 | 
						|
+	uint32_t dieidx;
 | 
						|
+
 | 
						|
+	if (!snf->select_die)
 | 
						|
+		return addr;
 | 
						|
+
 | 
						|
+	dieidx = addr >> snf->die_shift;
 | 
						|
+
 | 
						|
+	mtk_snand_select_die(snf, dieidx);
 | 
						|
+
 | 
						|
+	return addr & snf->die_mask;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static uint32_t mtk_snand_get_plane_address(struct mtk_snand *snf,
 | 
						|
+					    uint32_t page)
 | 
						|
+{
 | 
						|
+	uint32_t pages_per_block;
 | 
						|
+
 | 
						|
+	pages_per_block = 1 << (snf->erasesize_shift - snf->writesize_shift);
 | 
						|
+
 | 
						|
+	if (page & pages_per_block)
 | 
						|
+		return 1 << (snf->writesize_shift + 1);
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_page_op(struct mtk_snand *snf, uint32_t page, uint8_t cmd)
 | 
						|
+{
 | 
						|
+	uint8_t op[4];
 | 
						|
+
 | 
						|
+	op[0] = cmd;
 | 
						|
+	op[1] = (page >> 16) & 0xff;
 | 
						|
+	op[2] = (page >> 8) & 0xff;
 | 
						|
+	op[3] = page & 0xff;
 | 
						|
+
 | 
						|
+	return mtk_snand_mac_io(snf, op, sizeof(op), NULL, 0);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void mtk_snand_read_fdm(struct mtk_snand *snf, uint8_t *buf)
 | 
						|
+{
 | 
						|
+	uint32_t vall, valm;
 | 
						|
+	uint8_t *oobptr = buf;
 | 
						|
+	int i, j;
 | 
						|
+
 | 
						|
+	for (i = 0; i < snf->ecc_steps; i++) {
 | 
						|
+		vall = nfi_read32(snf, NFI_FDML(i));
 | 
						|
+		valm = nfi_read32(snf, NFI_FDMM(i));
 | 
						|
+
 | 
						|
+		for (j = 0; j < snf->nfi_soc->fdm_size; j++)
 | 
						|
+			oobptr[j] = (j >= 4 ? valm : vall) >> ((j % 4) * 8);
 | 
						|
+
 | 
						|
+		oobptr += snf->nfi_soc->fdm_size;
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_read_ecc_parity(struct mtk_snand *snf, uint32_t page,
 | 
						|
+				     uint32_t sect, uint8_t *oob)
 | 
						|
+{
 | 
						|
+	uint32_t ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size;
 | 
						|
+	uint32_t coladdr, raw_offs, offs;
 | 
						|
+	uint8_t op[4];
 | 
						|
+
 | 
						|
+	if (sizeof(op) + ecc_bytes > SNF_GPRAM_SIZE) {
 | 
						|
+		snand_log_snfi(snf->pdev,
 | 
						|
+			       "ECC parity size does not fit the GPRAM\n");
 | 
						|
+		return -ENOTSUPP;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	raw_offs = sect * snf->raw_sector_size + snf->nfi_soc->sector_size +
 | 
						|
+		   snf->nfi_soc->fdm_size;
 | 
						|
+	offs = snf->ecc_steps * snf->nfi_soc->fdm_size + sect * ecc_bytes;
 | 
						|
+
 | 
						|
+	/* Column address with plane bit */
 | 
						|
+	coladdr = raw_offs | mtk_snand_get_plane_address(snf, page);
 | 
						|
+
 | 
						|
+	op[0] = SNAND_CMD_READ_FROM_CACHE;
 | 
						|
+	op[1] = (coladdr >> 8) & 0xff;
 | 
						|
+	op[2] = coladdr & 0xff;
 | 
						|
+	op[3] = 0;
 | 
						|
+
 | 
						|
+	return mtk_snand_mac_io(snf, op, sizeof(op), oob + offs, ecc_bytes);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_check_ecc_result(struct mtk_snand *snf, uint32_t page)
 | 
						|
+{
 | 
						|
+	uint8_t *oob = snf->page_cache + snf->writesize;
 | 
						|
+	int i, rc, ret = 0, max_bitflips = 0;
 | 
						|
+
 | 
						|
+	for (i = 0; i < snf->ecc_steps; i++) {
 | 
						|
+		if (snf->sect_bf[i] >= 0) {
 | 
						|
+			if (snf->sect_bf[i] > max_bitflips)
 | 
						|
+				max_bitflips = snf->sect_bf[i];
 | 
						|
+			continue;
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		rc = mtk_snand_read_ecc_parity(snf, page, i, oob);
 | 
						|
+		if (rc)
 | 
						|
+			return rc;
 | 
						|
+
 | 
						|
+		rc = mtk_ecc_fixup_empty_sector(snf, i);
 | 
						|
+		if (rc < 0) {
 | 
						|
+			ret = -EBADMSG;
 | 
						|
+
 | 
						|
+			snand_log_ecc(snf->pdev,
 | 
						|
+			      "Uncorrectable bitflips in page %u sect %u\n",
 | 
						|
+			      page, i);
 | 
						|
+		} else if (rc) {
 | 
						|
+			snf->sect_bf[i] = rc;
 | 
						|
+
 | 
						|
+			if (snf->sect_bf[i] > max_bitflips)
 | 
						|
+				max_bitflips = snf->sect_bf[i];
 | 
						|
+
 | 
						|
+			snand_log_ecc(snf->pdev,
 | 
						|
+			      "%u bitflip%s corrected in page %u sect %u\n",
 | 
						|
+			      rc, rc > 1 ? "s" : "", page, i);
 | 
						|
+		} else {
 | 
						|
+			snf->sect_bf[i] = 0;
 | 
						|
+		}
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return ret ? ret : max_bitflips;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_read_cache(struct mtk_snand *snf, uint32_t page, bool raw)
 | 
						|
+{
 | 
						|
+	uint32_t coladdr, rwbytes, mode, len, val;
 | 
						|
+	uintptr_t dma_addr;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	/* Column address with plane bit */
 | 
						|
+	coladdr = mtk_snand_get_plane_address(snf, page);
 | 
						|
+
 | 
						|
+	mtk_snand_mac_reset(snf);
 | 
						|
+	mtk_nfi_reset(snf);
 | 
						|
+
 | 
						|
+	/* Command and dummy cycles */
 | 
						|
+	nfi_write32(snf, SNF_RD_CTL2,
 | 
						|
+		    ((uint32_t)snf->dummy_rfc << DATA_READ_DUMMY_S) |
 | 
						|
+		    (snf->opcode_rfc << DATA_READ_CMD_S));
 | 
						|
+
 | 
						|
+	/* Column address */
 | 
						|
+	nfi_write32(snf, SNF_RD_CTL3, coladdr);
 | 
						|
+
 | 
						|
+	/* Set read mode */
 | 
						|
+	mode = (uint32_t)snf->mode_rfc << DATA_READ_MODE_S;
 | 
						|
+	nfi_rmw32(snf, SNF_MISC_CTL, DATA_READ_MODE,
 | 
						|
+			mode | DATARD_CUSTOM_EN | (snf->nfi_soc->latch_lat << LATCH_LAT_S));
 | 
						|
+
 | 
						|
+	/* Set bytes to read */
 | 
						|
+	rwbytes = snf->ecc_steps * snf->raw_sector_size;
 | 
						|
+	nfi_write32(snf, SNF_MISC_CTL2, (rwbytes << PROGRAM_LOAD_BYTE_NUM_S) |
 | 
						|
+		    rwbytes);
 | 
						|
+
 | 
						|
+	/* NFI read prepare */
 | 
						|
+	mode = raw ? 0 : CNFG_HW_ECC_EN | CNFG_AUTO_FMT_EN;
 | 
						|
+	nfi_write16(snf, NFI_CNFG, (CNFG_OP_MODE_CUST << CNFG_OP_MODE_S) |
 | 
						|
+		    CNFG_DMA_BURST_EN | CNFG_READ_MODE | CNFG_DMA_MODE | mode);
 | 
						|
+
 | 
						|
+	nfi_write32(snf, NFI_CON, (snf->ecc_steps << CON_SEC_NUM_S));
 | 
						|
+
 | 
						|
+	/* Prepare for DMA read */
 | 
						|
+	len = snf->writesize + snf->oobsize;
 | 
						|
+	ret = dma_mem_map(snf->pdev, snf->page_cache, &dma_addr, len, false);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_nfi(snf->pdev,
 | 
						|
+			      "DMA map from device failed with %d\n", ret);
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	nfi_write32(snf, NFI_STRADDR, (uint32_t)dma_addr);
 | 
						|
+
 | 
						|
+	if (!raw)
 | 
						|
+		mtk_snand_ecc_decoder_start(snf);
 | 
						|
+
 | 
						|
+	/* Prepare for custom read interrupt */
 | 
						|
+	nfi_write32(snf, NFI_INTR_EN, NFI_IRQ_INTR_EN | NFI_IRQ_CUS_READ);
 | 
						|
+	irq_completion_init(snf->pdev);
 | 
						|
+
 | 
						|
+	/* Trigger NFI into custom mode */
 | 
						|
+	nfi_write16(snf, NFI_CMD, NFI_CMD_DUMMY_READ);
 | 
						|
+
 | 
						|
+	/* Start DMA read */
 | 
						|
+	nfi_rmw32(snf, NFI_CON, 0, CON_BRD);
 | 
						|
+	nfi_write16(snf, NFI_STRDATA, STR_DATA);
 | 
						|
+
 | 
						|
+	/* Wait for operation finished */
 | 
						|
+	ret = irq_completion_wait(snf->pdev, snf->nfi_base + SNF_STA_CTL1,
 | 
						|
+				  CUS_READ_DONE, SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_nfi(snf->pdev,
 | 
						|
+			      "DMA timed out for reading from cache\n");
 | 
						|
+		goto cleanup;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	/* Wait for BUS_SEC_CNTR returning expected value */
 | 
						|
+	ret = read32_poll_timeout(snf->nfi_base + NFI_BYTELEN, val,
 | 
						|
+				  BUS_SEC_CNTR(val) >= snf->ecc_steps,
 | 
						|
+				  0, SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_nfi(snf->pdev,
 | 
						|
+			      "Timed out waiting for BUS_SEC_CNTR\n");
 | 
						|
+		goto cleanup;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	/* Wait for bus becoming idle */
 | 
						|
+	ret = read32_poll_timeout(snf->nfi_base + NFI_MASTERSTA, val,
 | 
						|
+				  !(val & snf->nfi_soc->mastersta_mask),
 | 
						|
+				  0, SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_nfi(snf->pdev,
 | 
						|
+			      "Timed out waiting for bus becoming idle\n");
 | 
						|
+		goto cleanup;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (!raw) {
 | 
						|
+		ret = mtk_ecc_wait_decoder_done(snf);
 | 
						|
+		if (ret)
 | 
						|
+			goto cleanup;
 | 
						|
+
 | 
						|
+		mtk_snand_read_fdm(snf, snf->page_cache + snf->writesize);
 | 
						|
+
 | 
						|
+		mtk_ecc_check_decode_error(snf);
 | 
						|
+		mtk_snand_ecc_decoder_stop(snf);
 | 
						|
+
 | 
						|
+		ret = mtk_snand_check_ecc_result(snf, page);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+cleanup:
 | 
						|
+	/* DMA cleanup */
 | 
						|
+	dma_mem_unmap(snf->pdev, dma_addr, len, false);
 | 
						|
+
 | 
						|
+	/* Stop read */
 | 
						|
+	nfi_write32(snf, NFI_CON, 0);
 | 
						|
+	nfi_write16(snf, NFI_CNFG, 0);
 | 
						|
+
 | 
						|
+	/* Clear SNF done flag */
 | 
						|
+	nfi_rmw32(snf, SNF_STA_CTL1, 0, CUS_READ_DONE);
 | 
						|
+	nfi_write32(snf, SNF_STA_CTL1, 0);
 | 
						|
+
 | 
						|
+	/* Disable interrupt */
 | 
						|
+	nfi_read32(snf, NFI_INTR_STA);
 | 
						|
+	nfi_write32(snf, NFI_INTR_EN, 0);
 | 
						|
+
 | 
						|
+	nfi_rmw32(snf, SNF_MISC_CTL, DATARD_CUSTOM_EN | LATCH_LAT, 0);
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void mtk_snand_from_raw_page(struct mtk_snand *snf, void *buf, void *oob)
 | 
						|
+{
 | 
						|
+	uint32_t i, ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size;
 | 
						|
+	uint8_t *eccptr = oob + snf->ecc_steps * snf->nfi_soc->fdm_size;
 | 
						|
+	uint8_t *bufptr = buf, *oobptr = oob, *raw_sector;
 | 
						|
+
 | 
						|
+	for (i = 0; i < snf->ecc_steps; i++) {
 | 
						|
+		raw_sector = snf->page_cache + i * snf->raw_sector_size;
 | 
						|
+
 | 
						|
+		if (buf) {
 | 
						|
+			memcpy(bufptr, raw_sector, snf->nfi_soc->sector_size);
 | 
						|
+			bufptr += snf->nfi_soc->sector_size;
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		raw_sector += snf->nfi_soc->sector_size;
 | 
						|
+
 | 
						|
+		if (oob) {
 | 
						|
+			memcpy(oobptr, raw_sector, snf->nfi_soc->fdm_size);
 | 
						|
+			oobptr += snf->nfi_soc->fdm_size;
 | 
						|
+			raw_sector += snf->nfi_soc->fdm_size;
 | 
						|
+
 | 
						|
+			memcpy(eccptr, raw_sector, ecc_bytes);
 | 
						|
+			eccptr += ecc_bytes;
 | 
						|
+		}
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_do_read_page(struct mtk_snand *snf, uint64_t addr,
 | 
						|
+				  void *buf, void *oob, bool raw, bool format)
 | 
						|
+{
 | 
						|
+	uint64_t die_addr;
 | 
						|
+	uint32_t page, dly_ctrl3;
 | 
						|
+	int ret, retry_cnt = 0;
 | 
						|
+
 | 
						|
+	die_addr = mtk_snand_select_die_address(snf, addr);
 | 
						|
+	page = die_addr >> snf->writesize_shift;
 | 
						|
+
 | 
						|
+	dly_ctrl3 = nfi_read32(snf, SNF_DLY_CTL3);
 | 
						|
+
 | 
						|
+	ret = mtk_snand_page_op(snf, page, SNAND_CMD_READ_TO_CACHE);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_poll_status(snf, SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret < 0) {
 | 
						|
+		snand_log_chip(snf->pdev, "Read to cache command timed out\n");
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+retry:
 | 
						|
+	ret = mtk_snand_read_cache(snf, page, raw);
 | 
						|
+	if (ret < 0 && ret != -EBADMSG)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	if (ret == -EBADMSG && retry_cnt < 16) {
 | 
						|
+		nfi_write32(snf, SNF_DLY_CTL3, retry_cnt * 2);
 | 
						|
+		retry_cnt++;
 | 
						|
+		goto retry;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (retry_cnt) {
 | 
						|
+		if(ret == -EBADMSG) {
 | 
						|
+			nfi_write32(snf, SNF_DLY_CTL3, dly_ctrl3);
 | 
						|
+			snand_log_chip(snf->pdev,
 | 
						|
+				       "NFI calibration failed. Original sample delay: 0x%x\n",
 | 
						|
+				       dly_ctrl3);
 | 
						|
+		} else {
 | 
						|
+			snand_log_chip(snf->pdev,
 | 
						|
+				       "NFI calibration passed. New sample delay: 0x%x\n",
 | 
						|
+				       nfi_read32(snf, SNF_DLY_CTL3));
 | 
						|
+		}
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (raw) {
 | 
						|
+		if (format) {
 | 
						|
+			mtk_snand_bm_swap_raw(snf);
 | 
						|
+			mtk_snand_fdm_bm_swap_raw(snf);
 | 
						|
+			mtk_snand_from_raw_page(snf, buf, oob);
 | 
						|
+		} else {
 | 
						|
+			if (buf)
 | 
						|
+				memcpy(buf, snf->page_cache, snf->writesize);
 | 
						|
+
 | 
						|
+			if (oob) {
 | 
						|
+				memset(oob, 0xff, snf->oobsize);
 | 
						|
+				memcpy(oob, snf->page_cache + snf->writesize,
 | 
						|
+				       snf->ecc_steps * snf->spare_per_sector);
 | 
						|
+			}
 | 
						|
+		}
 | 
						|
+	} else {
 | 
						|
+		mtk_snand_bm_swap(snf);
 | 
						|
+		mtk_snand_fdm_bm_swap(snf);
 | 
						|
+
 | 
						|
+		if (buf)
 | 
						|
+			memcpy(buf, snf->page_cache, snf->writesize);
 | 
						|
+
 | 
						|
+		if (oob) {
 | 
						|
+			memset(oob, 0xff, snf->oobsize);
 | 
						|
+			memcpy(oob, snf->page_cache + snf->writesize,
 | 
						|
+			       snf->ecc_steps * snf->nfi_soc->fdm_size);
 | 
						|
+		}
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_read_page(struct mtk_snand *snf, uint64_t addr, void *buf,
 | 
						|
+			void *oob, bool raw)
 | 
						|
+{
 | 
						|
+	if (!snf || (!buf && !oob))
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	if (addr >= snf->size)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	return mtk_snand_do_read_page(snf, addr, buf, oob, raw, true);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void mtk_snand_write_fdm(struct mtk_snand *snf, const uint8_t *buf)
 | 
						|
+{
 | 
						|
+	uint32_t vall, valm, fdm_size = snf->nfi_soc->fdm_size;
 | 
						|
+	const uint8_t *oobptr = buf;
 | 
						|
+	int i, j;
 | 
						|
+
 | 
						|
+	for (i = 0; i < snf->ecc_steps; i++) {
 | 
						|
+		vall = 0;
 | 
						|
+		valm = 0;
 | 
						|
+
 | 
						|
+		for (j = 0; j < 8; j++) {
 | 
						|
+			if (j < 4)
 | 
						|
+				vall |= (j < fdm_size ? oobptr[j] : 0xff)
 | 
						|
+						<< (j * 8);
 | 
						|
+			else
 | 
						|
+				valm |= (j < fdm_size ? oobptr[j] : 0xff)
 | 
						|
+						<< ((j - 4) * 8);
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		nfi_write32(snf, NFI_FDML(i), vall);
 | 
						|
+		nfi_write32(snf, NFI_FDMM(i), valm);
 | 
						|
+
 | 
						|
+		oobptr += fdm_size;
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_program_load(struct mtk_snand *snf, uint32_t page,
 | 
						|
+				  bool raw)
 | 
						|
+{
 | 
						|
+	uint32_t coladdr, rwbytes, mode, len, val;
 | 
						|
+	uintptr_t dma_addr;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	/* Column address with plane bit */
 | 
						|
+	coladdr = mtk_snand_get_plane_address(snf, page);
 | 
						|
+
 | 
						|
+	mtk_snand_mac_reset(snf);
 | 
						|
+	mtk_nfi_reset(snf);
 | 
						|
+
 | 
						|
+	/* Write FDM registers if necessary */
 | 
						|
+	if (!raw)
 | 
						|
+		mtk_snand_write_fdm(snf, snf->page_cache + snf->writesize);
 | 
						|
+
 | 
						|
+	/* Command */
 | 
						|
+	nfi_write32(snf, SNF_PG_CTL1, (snf->opcode_pl << PG_LOAD_CMD_S));
 | 
						|
+
 | 
						|
+	/* Column address */
 | 
						|
+	nfi_write32(snf, SNF_PG_CTL2, coladdr);
 | 
						|
+
 | 
						|
+	/* Set write mode */
 | 
						|
+	mode = snf->mode_pl ? PG_LOAD_X4_EN : 0;
 | 
						|
+	nfi_rmw32(snf, SNF_MISC_CTL, PG_LOAD_X4_EN, mode | PG_LOAD_CUSTOM_EN);
 | 
						|
+
 | 
						|
+	/* Set bytes to write */
 | 
						|
+	rwbytes = snf->ecc_steps * snf->raw_sector_size;
 | 
						|
+	nfi_write32(snf, SNF_MISC_CTL2, (rwbytes << PROGRAM_LOAD_BYTE_NUM_S) |
 | 
						|
+		    rwbytes);
 | 
						|
+
 | 
						|
+	/* NFI write prepare */
 | 
						|
+	mode = raw ? 0 : CNFG_HW_ECC_EN | CNFG_AUTO_FMT_EN;
 | 
						|
+	nfi_write16(snf, NFI_CNFG, (CNFG_OP_MODE_PROGRAM << CNFG_OP_MODE_S) |
 | 
						|
+		    CNFG_DMA_BURST_EN | CNFG_DMA_MODE | mode);
 | 
						|
+
 | 
						|
+	nfi_write32(snf, NFI_CON, (snf->ecc_steps << CON_SEC_NUM_S));
 | 
						|
+
 | 
						|
+	/* Prepare for DMA write */
 | 
						|
+	len = snf->writesize + snf->oobsize;
 | 
						|
+	ret = dma_mem_map(snf->pdev, snf->page_cache, &dma_addr, len, true);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_nfi(snf->pdev,
 | 
						|
+			      "DMA map to device failed with %d\n", ret);
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	nfi_write32(snf, NFI_STRADDR, (uint32_t)dma_addr);
 | 
						|
+
 | 
						|
+	if (!raw)
 | 
						|
+		mtk_snand_ecc_encoder_start(snf);
 | 
						|
+
 | 
						|
+	/* Prepare for custom write interrupt */
 | 
						|
+	nfi_write32(snf, NFI_INTR_EN, NFI_IRQ_INTR_EN | NFI_IRQ_CUS_PG);
 | 
						|
+	irq_completion_init(snf->pdev);
 | 
						|
+
 | 
						|
+	/* Trigger NFI into custom mode */
 | 
						|
+	nfi_write16(snf, NFI_CMD, NFI_CMD_DUMMY_WRITE);
 | 
						|
+
 | 
						|
+	/* Start DMA write */
 | 
						|
+	nfi_rmw32(snf, NFI_CON, 0, CON_BWR);
 | 
						|
+	nfi_write16(snf, NFI_STRDATA, STR_DATA);
 | 
						|
+
 | 
						|
+	/* Wait for operation finished */
 | 
						|
+	ret = irq_completion_wait(snf->pdev, snf->nfi_base + SNF_STA_CTL1,
 | 
						|
+				  CUS_PG_DONE, SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_nfi(snf->pdev,
 | 
						|
+			      "DMA timed out for program load\n");
 | 
						|
+		goto cleanup;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	/* Wait for NFI_SEC_CNTR returning expected value */
 | 
						|
+	ret = read32_poll_timeout(snf->nfi_base + NFI_ADDRCNTR, val,
 | 
						|
+				  NFI_SEC_CNTR(val) >= snf->ecc_steps,
 | 
						|
+				  0, SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_nfi(snf->pdev,
 | 
						|
+			      "Timed out waiting for BUS_SEC_CNTR\n");
 | 
						|
+		goto cleanup;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (!raw)
 | 
						|
+		mtk_snand_ecc_encoder_stop(snf);
 | 
						|
+
 | 
						|
+cleanup:
 | 
						|
+	/* DMA cleanup */
 | 
						|
+	dma_mem_unmap(snf->pdev, dma_addr, len, true);
 | 
						|
+
 | 
						|
+	/* Stop write */
 | 
						|
+	nfi_write32(snf, NFI_CON, 0);
 | 
						|
+	nfi_write16(snf, NFI_CNFG, 0);
 | 
						|
+
 | 
						|
+	/* Clear SNF done flag */
 | 
						|
+	nfi_rmw32(snf, SNF_STA_CTL1, 0, CUS_PG_DONE);
 | 
						|
+	nfi_write32(snf, SNF_STA_CTL1, 0);
 | 
						|
+
 | 
						|
+	/* Disable interrupt */
 | 
						|
+	nfi_read32(snf, NFI_INTR_STA);
 | 
						|
+	nfi_write32(snf, NFI_INTR_EN, 0);
 | 
						|
+
 | 
						|
+	nfi_rmw32(snf, SNF_MISC_CTL, PG_LOAD_CUSTOM_EN, 0);
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void mtk_snand_to_raw_page(struct mtk_snand *snf,
 | 
						|
+				  const void *buf, const void *oob,
 | 
						|
+				  bool empty_ecc)
 | 
						|
+{
 | 
						|
+	uint32_t i, ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size;
 | 
						|
+	const uint8_t *eccptr = oob + snf->ecc_steps * snf->nfi_soc->fdm_size;
 | 
						|
+	const uint8_t *bufptr = buf, *oobptr = oob;
 | 
						|
+	uint8_t *raw_sector;
 | 
						|
+
 | 
						|
+	memset(snf->page_cache, 0xff, snf->writesize + snf->oobsize);
 | 
						|
+	for (i = 0; i < snf->ecc_steps; i++) {
 | 
						|
+		raw_sector = snf->page_cache + i * snf->raw_sector_size;
 | 
						|
+
 | 
						|
+		if (buf) {
 | 
						|
+			memcpy(raw_sector, bufptr, snf->nfi_soc->sector_size);
 | 
						|
+			bufptr += snf->nfi_soc->sector_size;
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		raw_sector += snf->nfi_soc->sector_size;
 | 
						|
+
 | 
						|
+		if (oob) {
 | 
						|
+			memcpy(raw_sector, oobptr, snf->nfi_soc->fdm_size);
 | 
						|
+			oobptr += snf->nfi_soc->fdm_size;
 | 
						|
+			raw_sector += snf->nfi_soc->fdm_size;
 | 
						|
+
 | 
						|
+			if (empty_ecc)
 | 
						|
+				memset(raw_sector, 0xff, ecc_bytes);
 | 
						|
+			else
 | 
						|
+				memcpy(raw_sector, eccptr, ecc_bytes);
 | 
						|
+			eccptr += ecc_bytes;
 | 
						|
+		}
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
+static bool mtk_snand_is_empty_page(struct mtk_snand *snf, const void *buf,
 | 
						|
+				    const void *oob)
 | 
						|
+{
 | 
						|
+	const uint8_t *p = buf;
 | 
						|
+	uint32_t i, j;
 | 
						|
+
 | 
						|
+	if (buf) {
 | 
						|
+		for (i = 0; i < snf->writesize; i++) {
 | 
						|
+			if (p[i] != 0xff)
 | 
						|
+				return false;
 | 
						|
+		}
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (oob) {
 | 
						|
+		for (j = 0; j < snf->ecc_steps; j++) {
 | 
						|
+			p = oob + j * snf->nfi_soc->fdm_size;
 | 
						|
+
 | 
						|
+			for (i = 0; i < snf->nfi_soc->fdm_ecc_size; i++) {
 | 
						|
+				if (p[i] != 0xff)
 | 
						|
+					return false;
 | 
						|
+			}
 | 
						|
+		}
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return true;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_do_write_page(struct mtk_snand *snf, uint64_t addr,
 | 
						|
+				   const void *buf, const void *oob,
 | 
						|
+				   bool raw, bool format)
 | 
						|
+{
 | 
						|
+	uint64_t die_addr;
 | 
						|
+	bool empty_ecc = false;
 | 
						|
+	uint32_t page;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	die_addr = mtk_snand_select_die_address(snf, addr);
 | 
						|
+	page = die_addr >> snf->writesize_shift;
 | 
						|
+
 | 
						|
+	if (!raw && mtk_snand_is_empty_page(snf, buf, oob)) {
 | 
						|
+		/*
 | 
						|
+		 * If the data in the page to be ecc-ed is full 0xff,
 | 
						|
+		 * change to raw write mode
 | 
						|
+		 */
 | 
						|
+		raw = true;
 | 
						|
+		format = true;
 | 
						|
+
 | 
						|
+		/* fill ecc parity code region with 0xff */
 | 
						|
+		empty_ecc = true;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (raw) {
 | 
						|
+		if (format) {
 | 
						|
+			mtk_snand_to_raw_page(snf, buf, oob, empty_ecc);
 | 
						|
+			mtk_snand_fdm_bm_swap_raw(snf);
 | 
						|
+			mtk_snand_bm_swap_raw(snf);
 | 
						|
+		} else {
 | 
						|
+			memset(snf->page_cache, 0xff,
 | 
						|
+			       snf->writesize + snf->oobsize);
 | 
						|
+
 | 
						|
+			if (buf)
 | 
						|
+				memcpy(snf->page_cache, buf, snf->writesize);
 | 
						|
+
 | 
						|
+			if (oob) {
 | 
						|
+				memcpy(snf->page_cache + snf->writesize, oob,
 | 
						|
+				       snf->ecc_steps * snf->spare_per_sector);
 | 
						|
+			}
 | 
						|
+		}
 | 
						|
+	} else {
 | 
						|
+		memset(snf->page_cache, 0xff, snf->writesize + snf->oobsize);
 | 
						|
+		if (buf)
 | 
						|
+			memcpy(snf->page_cache, buf, snf->writesize);
 | 
						|
+
 | 
						|
+		if (oob) {
 | 
						|
+			memcpy(snf->page_cache + snf->writesize, oob,
 | 
						|
+			       snf->ecc_steps * snf->nfi_soc->fdm_size);
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		mtk_snand_fdm_bm_swap(snf);
 | 
						|
+		mtk_snand_bm_swap(snf);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	ret = mtk_snand_write_enable(snf);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_program_load(snf, page, raw);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_page_op(snf, page, SNAND_CMD_PROGRAM_EXECUTE);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_poll_status(snf, SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret < 0) {
 | 
						|
+		snand_log_chip(snf->pdev,
 | 
						|
+			       "Page program command timed out on page %u\n",
 | 
						|
+			       page);
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (ret & SNAND_STATUS_PROGRAM_FAIL) {
 | 
						|
+		snand_log_chip(snf->pdev,
 | 
						|
+			       "Page program failed on page %u\n", page);
 | 
						|
+		return -EIO;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_write_page(struct mtk_snand *snf, uint64_t addr, const void *buf,
 | 
						|
+			 const void *oob, bool raw)
 | 
						|
+{
 | 
						|
+	if (!snf || (!buf && !oob))
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	if (addr >= snf->size)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	return mtk_snand_do_write_page(snf, addr, buf, oob, raw, true);
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_erase_block(struct mtk_snand *snf, uint64_t addr)
 | 
						|
+{
 | 
						|
+	uint64_t die_addr;
 | 
						|
+	uint32_t page, block;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	if (!snf)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	if (addr >= snf->size)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	die_addr = mtk_snand_select_die_address(snf, addr);
 | 
						|
+	block = die_addr >> snf->erasesize_shift;
 | 
						|
+	page = block << (snf->erasesize_shift - snf->writesize_shift);
 | 
						|
+
 | 
						|
+	ret = mtk_snand_write_enable(snf);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_page_op(snf, page, SNAND_CMD_BLOCK_ERASE);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_poll_status(snf, SNFI_POLL_INTERVAL);
 | 
						|
+	if (ret < 0) {
 | 
						|
+		snand_log_chip(snf->pdev,
 | 
						|
+			       "Block erase command timed out on block %u\n",
 | 
						|
+			       block);
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (ret & SNAND_STATUS_ERASE_FAIL) {
 | 
						|
+		snand_log_chip(snf->pdev,
 | 
						|
+			       "Block erase failed on block %u\n", block);
 | 
						|
+		return -EIO;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_block_isbad_std(struct mtk_snand *snf, uint64_t addr)
 | 
						|
+{
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_do_read_page(snf, addr, NULL, snf->buf_cache, true,
 | 
						|
+				     false);
 | 
						|
+	if (ret && ret != -EBADMSG)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	return snf->buf_cache[0] != 0xff;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_block_isbad_mtk(struct mtk_snand *snf, uint64_t addr)
 | 
						|
+{
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_do_read_page(snf, addr, NULL, snf->buf_cache, true,
 | 
						|
+				     true);
 | 
						|
+	if (ret && ret != -EBADMSG)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	return snf->buf_cache[0] != 0xff;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_block_isbad(struct mtk_snand *snf, uint64_t addr)
 | 
						|
+{
 | 
						|
+	if (!snf)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	if (addr >= snf->size)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	addr &= ~snf->erasesize_mask;
 | 
						|
+
 | 
						|
+	if (snf->nfi_soc->bbm_swap)
 | 
						|
+		return mtk_snand_block_isbad_std(snf, addr);
 | 
						|
+
 | 
						|
+	return mtk_snand_block_isbad_mtk(snf, addr);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_block_markbad_std(struct mtk_snand *snf, uint64_t addr)
 | 
						|
+{
 | 
						|
+	/* Standard BBM position */
 | 
						|
+	memset(snf->buf_cache, 0xff, snf->oobsize);
 | 
						|
+	snf->buf_cache[0] = 0;
 | 
						|
+
 | 
						|
+	return mtk_snand_do_write_page(snf, addr, NULL, snf->buf_cache, true,
 | 
						|
+				       false);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_block_markbad_mtk(struct mtk_snand *snf, uint64_t addr)
 | 
						|
+{
 | 
						|
+	/* Write the whole page with zeros */
 | 
						|
+	memset(snf->buf_cache, 0, snf->writesize + snf->oobsize);
 | 
						|
+
 | 
						|
+	return mtk_snand_do_write_page(snf, addr, snf->buf_cache,
 | 
						|
+				       snf->buf_cache + snf->writesize, true,
 | 
						|
+				       true);
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_block_markbad(struct mtk_snand *snf, uint64_t addr)
 | 
						|
+{
 | 
						|
+	if (!snf)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	if (addr >= snf->size)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	addr &= ~snf->erasesize_mask;
 | 
						|
+
 | 
						|
+	if (snf->nfi_soc->bbm_swap)
 | 
						|
+		return mtk_snand_block_markbad_std(snf, addr);
 | 
						|
+
 | 
						|
+	return mtk_snand_block_markbad_mtk(snf, addr);
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_fill_oob(struct mtk_snand *snf, uint8_t *oobraw,
 | 
						|
+		       const uint8_t *oobbuf, size_t ooblen)
 | 
						|
+{
 | 
						|
+	size_t len = ooblen, sect_fdm_len;
 | 
						|
+	const uint8_t *oob = oobbuf;
 | 
						|
+	uint32_t step = 0;
 | 
						|
+
 | 
						|
+	if (!snf || !oobraw || !oob)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	while (len && step < snf->ecc_steps) {
 | 
						|
+		sect_fdm_len = snf->nfi_soc->fdm_size - 1;
 | 
						|
+		if (sect_fdm_len > len)
 | 
						|
+			sect_fdm_len = len;
 | 
						|
+
 | 
						|
+		memcpy(oobraw + step * snf->nfi_soc->fdm_size + 1, oob,
 | 
						|
+		       sect_fdm_len);
 | 
						|
+
 | 
						|
+		len -= sect_fdm_len;
 | 
						|
+		oob += sect_fdm_len;
 | 
						|
+		step++;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return len;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_transfer_oob(struct mtk_snand *snf, uint8_t *oobbuf,
 | 
						|
+			   size_t ooblen, const uint8_t *oobraw)
 | 
						|
+{
 | 
						|
+	size_t len = ooblen, sect_fdm_len;
 | 
						|
+	uint8_t *oob = oobbuf;
 | 
						|
+	uint32_t step = 0;
 | 
						|
+
 | 
						|
+	if (!snf || !oobraw || !oob)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	while (len && step < snf->ecc_steps) {
 | 
						|
+		sect_fdm_len = snf->nfi_soc->fdm_size - 1;
 | 
						|
+		if (sect_fdm_len > len)
 | 
						|
+			sect_fdm_len = len;
 | 
						|
+
 | 
						|
+		memcpy(oob, oobraw + step * snf->nfi_soc->fdm_size + 1,
 | 
						|
+		       sect_fdm_len);
 | 
						|
+
 | 
						|
+		len -= sect_fdm_len;
 | 
						|
+		oob += sect_fdm_len;
 | 
						|
+		step++;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return len;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_read_page_auto_oob(struct mtk_snand *snf, uint64_t addr,
 | 
						|
+				 void *buf, void *oob, size_t ooblen,
 | 
						|
+				 size_t *actualooblen, bool raw)
 | 
						|
+{
 | 
						|
+	int ret, oobremain;
 | 
						|
+
 | 
						|
+	if (!snf)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	if (!oob)
 | 
						|
+		return mtk_snand_read_page(snf, addr, buf, NULL, raw);
 | 
						|
+
 | 
						|
+	ret = mtk_snand_read_page(snf, addr, buf, snf->buf_cache, raw);
 | 
						|
+	if (ret && ret != -EBADMSG) {
 | 
						|
+		if (actualooblen)
 | 
						|
+			*actualooblen = 0;
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	oobremain = mtk_snand_transfer_oob(snf, oob, ooblen, snf->buf_cache);
 | 
						|
+	if (actualooblen)
 | 
						|
+		*actualooblen = ooblen - oobremain;
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_write_page_auto_oob(struct mtk_snand *snf, uint64_t addr,
 | 
						|
+				  const void *buf, const void *oob,
 | 
						|
+				  size_t ooblen, size_t *actualooblen, bool raw)
 | 
						|
+{
 | 
						|
+	int oobremain;
 | 
						|
+
 | 
						|
+	if (!snf)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	if (!oob)
 | 
						|
+		return mtk_snand_write_page(snf, addr, buf, NULL, raw);
 | 
						|
+
 | 
						|
+	memset(snf->buf_cache, 0xff, snf->oobsize);
 | 
						|
+	oobremain = mtk_snand_fill_oob(snf, snf->buf_cache, oob, ooblen);
 | 
						|
+	if (actualooblen)
 | 
						|
+		*actualooblen = ooblen - oobremain;
 | 
						|
+
 | 
						|
+	return mtk_snand_write_page(snf, addr, buf, snf->buf_cache, raw);
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_get_chip_info(struct mtk_snand *snf,
 | 
						|
+			    struct mtk_snand_chip_info *info)
 | 
						|
+{
 | 
						|
+	if (!snf || !info)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	info->model = snf->model;
 | 
						|
+	info->chipsize = snf->size;
 | 
						|
+	info->blocksize = snf->erasesize;
 | 
						|
+	info->pagesize = snf->writesize;
 | 
						|
+	info->sparesize = snf->oobsize;
 | 
						|
+	info->spare_per_sector = snf->spare_per_sector;
 | 
						|
+	info->fdm_size = snf->nfi_soc->fdm_size;
 | 
						|
+	info->fdm_ecc_size = snf->nfi_soc->fdm_ecc_size;
 | 
						|
+	info->num_sectors = snf->ecc_steps;
 | 
						|
+	info->sector_size = snf->nfi_soc->sector_size;
 | 
						|
+	info->ecc_strength = snf->ecc_strength;
 | 
						|
+	info->ecc_bytes = snf->ecc_bytes;
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_irq_process(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	uint32_t sta, ien;
 | 
						|
+
 | 
						|
+	if (!snf)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	sta = nfi_read32(snf, NFI_INTR_STA);
 | 
						|
+	ien = nfi_read32(snf, NFI_INTR_EN);
 | 
						|
+
 | 
						|
+	if (!(sta & ien))
 | 
						|
+		return 0;
 | 
						|
+
 | 
						|
+	nfi_write32(snf, NFI_INTR_EN, 0);
 | 
						|
+	irq_completion_done(snf->pdev);
 | 
						|
+
 | 
						|
+	return 1;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_select_spare_per_sector(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	uint32_t spare_per_step = snf->oobsize / snf->ecc_steps;
 | 
						|
+	int i, mul = 1;
 | 
						|
+
 | 
						|
+	/*
 | 
						|
+	 * If we're using the 1KB sector size, HW will automatically
 | 
						|
+	 * double the spare size. So we should only use half of the value.
 | 
						|
+	 */
 | 
						|
+	if (snf->nfi_soc->sector_size == 1024)
 | 
						|
+		mul = 2;
 | 
						|
+
 | 
						|
+	spare_per_step /= mul;
 | 
						|
+
 | 
						|
+	for (i = snf->nfi_soc->num_spare_size - 1; i >= 0; i--) {
 | 
						|
+		if (snf->nfi_soc->spare_sizes[i] <= spare_per_step) {
 | 
						|
+			snf->spare_per_sector = snf->nfi_soc->spare_sizes[i];
 | 
						|
+			snf->spare_per_sector *= mul;
 | 
						|
+			return i;
 | 
						|
+		}
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	snand_log_nfi(snf->pdev,
 | 
						|
+		      "Page size %u+%u is not supported\n", snf->writesize,
 | 
						|
+		      snf->oobsize);
 | 
						|
+
 | 
						|
+	return -1;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_pagefmt_setup(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	uint32_t spare_size_idx, spare_size_shift, pagesize_idx;
 | 
						|
+	uint32_t sector_size_512;
 | 
						|
+
 | 
						|
+	if (snf->nfi_soc->sector_size == 512) {
 | 
						|
+		sector_size_512 = NFI_SEC_SEL_512;
 | 
						|
+		spare_size_shift = NFI_SPARE_SIZE_S;
 | 
						|
+	} else {
 | 
						|
+		sector_size_512 = 0;
 | 
						|
+		spare_size_shift = NFI_SPARE_SIZE_LS_S;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	switch (snf->writesize) {
 | 
						|
+	case SZ_512:
 | 
						|
+		pagesize_idx = NFI_PAGE_SIZE_512_2K;
 | 
						|
+		break;
 | 
						|
+	case SZ_2K:
 | 
						|
+		if (snf->nfi_soc->sector_size == 512)
 | 
						|
+			pagesize_idx = NFI_PAGE_SIZE_2K_4K;
 | 
						|
+		else
 | 
						|
+			pagesize_idx = NFI_PAGE_SIZE_512_2K;
 | 
						|
+		break;
 | 
						|
+	case SZ_4K:
 | 
						|
+		if (snf->nfi_soc->sector_size == 512)
 | 
						|
+			pagesize_idx = NFI_PAGE_SIZE_4K_8K;
 | 
						|
+		else
 | 
						|
+			pagesize_idx = NFI_PAGE_SIZE_2K_4K;
 | 
						|
+		break;
 | 
						|
+	case SZ_8K:
 | 
						|
+		if (snf->nfi_soc->sector_size == 512)
 | 
						|
+			pagesize_idx = NFI_PAGE_SIZE_8K_16K;
 | 
						|
+		else
 | 
						|
+			pagesize_idx = NFI_PAGE_SIZE_4K_8K;
 | 
						|
+		break;
 | 
						|
+	case SZ_16K:
 | 
						|
+		pagesize_idx = NFI_PAGE_SIZE_8K_16K;
 | 
						|
+		break;
 | 
						|
+	default:
 | 
						|
+		snand_log_nfi(snf->pdev, "Page size %u is not supported\n",
 | 
						|
+			      snf->writesize);
 | 
						|
+		return -ENOTSUPP;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	spare_size_idx = mtk_snand_select_spare_per_sector(snf);
 | 
						|
+	if (unlikely(spare_size_idx < 0))
 | 
						|
+		return -ENOTSUPP;
 | 
						|
+
 | 
						|
+	snf->raw_sector_size = snf->nfi_soc->sector_size +
 | 
						|
+			       snf->spare_per_sector;
 | 
						|
+
 | 
						|
+	/* Setup page format */
 | 
						|
+	nfi_write32(snf, NFI_PAGEFMT,
 | 
						|
+		    (snf->nfi_soc->fdm_ecc_size << NFI_FDM_ECC_NUM_S) |
 | 
						|
+		    (snf->nfi_soc->fdm_size << NFI_FDM_NUM_S) |
 | 
						|
+		    (spare_size_idx << spare_size_shift) |
 | 
						|
+		    (pagesize_idx << NFI_PAGE_SIZE_S) |
 | 
						|
+		    sector_size_512);
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static enum snand_flash_io mtk_snand_select_opcode(struct mtk_snand *snf,
 | 
						|
+				   uint32_t snfi_caps, uint8_t *opcode,
 | 
						|
+				   uint8_t *dummy,
 | 
						|
+				   const struct snand_io_cap *op_cap)
 | 
						|
+{
 | 
						|
+	uint32_t i, caps;
 | 
						|
+
 | 
						|
+	caps = snfi_caps & op_cap->caps;
 | 
						|
+
 | 
						|
+	i = fls(caps);
 | 
						|
+	if (i > 0) {
 | 
						|
+		*opcode = op_cap->opcodes[i - 1].opcode;
 | 
						|
+		if (dummy)
 | 
						|
+			*dummy = op_cap->opcodes[i - 1].dummy;
 | 
						|
+		return i - 1;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return __SNAND_IO_MAX;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_select_opcode_rfc(struct mtk_snand *snf,
 | 
						|
+				       uint32_t snfi_caps,
 | 
						|
+				       const struct snand_io_cap *op_cap)
 | 
						|
+{
 | 
						|
+	enum snand_flash_io idx;
 | 
						|
+
 | 
						|
+	static const uint8_t rfc_modes[__SNAND_IO_MAX] = {
 | 
						|
+		[SNAND_IO_1_1_1] = DATA_READ_MODE_X1,
 | 
						|
+		[SNAND_IO_1_1_2] = DATA_READ_MODE_X2,
 | 
						|
+		[SNAND_IO_1_2_2] = DATA_READ_MODE_DUAL,
 | 
						|
+		[SNAND_IO_1_1_4] = DATA_READ_MODE_X4,
 | 
						|
+		[SNAND_IO_1_4_4] = DATA_READ_MODE_QUAD,
 | 
						|
+	};
 | 
						|
+
 | 
						|
+	idx = mtk_snand_select_opcode(snf, snfi_caps, &snf->opcode_rfc,
 | 
						|
+				      &snf->dummy_rfc, op_cap);
 | 
						|
+	if (idx >= __SNAND_IO_MAX) {
 | 
						|
+		snand_log_snfi(snf->pdev,
 | 
						|
+			       "No capable opcode for read from cache\n");
 | 
						|
+		return -ENOTSUPP;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	snf->mode_rfc = rfc_modes[idx];
 | 
						|
+
 | 
						|
+	if (idx == SNAND_IO_1_1_4 || idx == SNAND_IO_1_4_4)
 | 
						|
+		snf->quad_spi_op = true;
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_select_opcode_pl(struct mtk_snand *snf, uint32_t snfi_caps,
 | 
						|
+				      const struct snand_io_cap *op_cap)
 | 
						|
+{
 | 
						|
+	enum snand_flash_io idx;
 | 
						|
+
 | 
						|
+	static const uint8_t pl_modes[__SNAND_IO_MAX] = {
 | 
						|
+		[SNAND_IO_1_1_1] = 0,
 | 
						|
+		[SNAND_IO_1_1_4] = 1,
 | 
						|
+	};
 | 
						|
+
 | 
						|
+	idx = mtk_snand_select_opcode(snf, snfi_caps, &snf->opcode_pl,
 | 
						|
+				      NULL, op_cap);
 | 
						|
+	if (idx >= __SNAND_IO_MAX) {
 | 
						|
+		snand_log_snfi(snf->pdev,
 | 
						|
+			       "No capable opcode for program load\n");
 | 
						|
+		return -ENOTSUPP;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	snf->mode_pl = pl_modes[idx];
 | 
						|
+
 | 
						|
+	if (idx == SNAND_IO_1_1_4)
 | 
						|
+		snf->quad_spi_op = true;
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_setup(struct mtk_snand *snf,
 | 
						|
+			   const struct snand_flash_info *snand_info)
 | 
						|
+{
 | 
						|
+	const struct snand_mem_org *memorg = &snand_info->memorg;
 | 
						|
+	uint32_t i, msg_size, snfi_caps;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	/* Calculate flash memory organization */
 | 
						|
+	snf->model = snand_info->model;
 | 
						|
+	snf->writesize = memorg->pagesize;
 | 
						|
+	snf->oobsize = memorg->sparesize;
 | 
						|
+	snf->erasesize = snf->writesize * memorg->pages_per_block;
 | 
						|
+	snf->die_size = (uint64_t)snf->erasesize * memorg->blocks_per_die;
 | 
						|
+	snf->size = snf->die_size * memorg->ndies;
 | 
						|
+	snf->num_dies = memorg->ndies;
 | 
						|
+
 | 
						|
+	snf->writesize_mask = snf->writesize - 1;
 | 
						|
+	snf->erasesize_mask = snf->erasesize - 1;
 | 
						|
+	snf->die_mask = snf->die_size - 1;
 | 
						|
+
 | 
						|
+	snf->writesize_shift = ffs(snf->writesize) - 1;
 | 
						|
+	snf->erasesize_shift = ffs(snf->erasesize) - 1;
 | 
						|
+	snf->die_shift = mtk_snand_ffs64(snf->die_size) - 1;
 | 
						|
+
 | 
						|
+	snf->select_die = snand_info->select_die;
 | 
						|
+
 | 
						|
+	/* Determine opcodes for read from cache/program load */
 | 
						|
+	snfi_caps = SPI_IO_1_1_1 | SPI_IO_1_1_2 | SPI_IO_1_2_2;
 | 
						|
+	if (snf->snfi_quad_spi)
 | 
						|
+		snfi_caps |= SPI_IO_1_1_4 | SPI_IO_1_4_4;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_select_opcode_rfc(snf, snfi_caps, snand_info->cap_rd);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	ret = mtk_snand_select_opcode_pl(snf, snfi_caps, snand_info->cap_pl);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	/* ECC and page format */
 | 
						|
+	snf->ecc_steps = snf->writesize / snf->nfi_soc->sector_size;
 | 
						|
+	if (snf->ecc_steps > snf->nfi_soc->max_sectors) {
 | 
						|
+		snand_log_nfi(snf->pdev, "Page size %u is not supported\n",
 | 
						|
+			      snf->writesize);
 | 
						|
+		return -ENOTSUPP;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	ret = mtk_snand_pagefmt_setup(snf);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	msg_size = snf->nfi_soc->sector_size + snf->nfi_soc->fdm_ecc_size;
 | 
						|
+	ret = mtk_ecc_setup(snf, snf->nfi_base + NFI_FDM0L,
 | 
						|
+			    snf->spare_per_sector - snf->nfi_soc->fdm_size,
 | 
						|
+			    msg_size);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	nfi_write16(snf, NFI_CNFG, 0);
 | 
						|
+
 | 
						|
+	/* Tuning options */
 | 
						|
+	nfi_write16(snf, NFI_DEBUG_CON1, WBUF_EN);
 | 
						|
+	nfi_write32(snf, SNF_DLY_CTL3, (snf->nfi_soc->sample_delay << SFCK_SAM_DLY_S));
 | 
						|
+
 | 
						|
+	/* Interrupts */
 | 
						|
+	nfi_read32(snf, NFI_INTR_STA);
 | 
						|
+	nfi_write32(snf, NFI_INTR_EN, 0);
 | 
						|
+
 | 
						|
+	/* Clear SNF done flag */
 | 
						|
+	nfi_rmw32(snf, SNF_STA_CTL1, 0, CUS_READ_DONE | CUS_PG_DONE);
 | 
						|
+	nfi_write32(snf, SNF_STA_CTL1, 0);
 | 
						|
+
 | 
						|
+	/* Initialization on all dies */
 | 
						|
+	for (i = 0; i < snf->num_dies; i++) {
 | 
						|
+		mtk_snand_select_die(snf, i);
 | 
						|
+
 | 
						|
+		/* Disable On-Die ECC engine */
 | 
						|
+		ret = mtk_snand_ondie_ecc_control(snf, false);
 | 
						|
+		if (ret)
 | 
						|
+			return ret;
 | 
						|
+
 | 
						|
+		/* Disable block protection */
 | 
						|
+		mtk_snand_unlock(snf);
 | 
						|
+
 | 
						|
+		/* Enable/disable quad-spi */
 | 
						|
+		mtk_snand_qspi_control(snf, snf->quad_spi_op);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	mtk_snand_select_die(snf, 0);
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int mtk_snand_id_probe(struct mtk_snand *snf,
 | 
						|
+			      const struct snand_flash_info **snand_info)
 | 
						|
+{
 | 
						|
+	uint8_t id[4], op[2];
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	/* Read SPI-NAND JEDEC ID, OP + dummy/addr + ID */
 | 
						|
+	op[0] = SNAND_CMD_READID;
 | 
						|
+	op[1] = 0;
 | 
						|
+	ret = mtk_snand_mac_io(snf, op, 2, id, sizeof(id));
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	*snand_info = snand_flash_id_lookup(SNAND_ID_DYMMY, id);
 | 
						|
+	if (*snand_info)
 | 
						|
+		return 0;
 | 
						|
+
 | 
						|
+	/* Read SPI-NAND JEDEC ID, OP + ID */
 | 
						|
+	op[0] = SNAND_CMD_READID;
 | 
						|
+	ret = mtk_snand_mac_io(snf, op, 1, id, sizeof(id));
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	*snand_info = snand_flash_id_lookup(SNAND_ID_DYMMY, id);
 | 
						|
+	if (*snand_info)
 | 
						|
+		return 0;
 | 
						|
+
 | 
						|
+	snand_log_chip(snf->pdev,
 | 
						|
+		       "Unrecognized SPI-NAND ID: %02x %02x %02x %02x\n",
 | 
						|
+		       id[0], id[1], id[2], id[3]);
 | 
						|
+
 | 
						|
+	return -EINVAL;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_init(void *dev, const struct mtk_snand_platdata *pdata,
 | 
						|
+		   struct mtk_snand **psnf)
 | 
						|
+{
 | 
						|
+	const struct snand_flash_info *snand_info;
 | 
						|
+	uint32_t rawpage_size, sect_bf_size;
 | 
						|
+	struct mtk_snand tmpsnf, *snf;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	if (!pdata || !psnf)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	if (pdata->soc >= __SNAND_SOC_MAX) {
 | 
						|
+		snand_log_chip(dev, "Invalid SOC %u for MTK-SNAND\n",
 | 
						|
+			       pdata->soc);
 | 
						|
+		return -EINVAL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	/* Dummy instance only for initial reset and id probe */
 | 
						|
+	tmpsnf.nfi_base = pdata->nfi_base;
 | 
						|
+	tmpsnf.ecc_base = pdata->ecc_base;
 | 
						|
+	tmpsnf.soc = pdata->soc;
 | 
						|
+	tmpsnf.nfi_soc = &mtk_snand_socs[pdata->soc];
 | 
						|
+	tmpsnf.pdev = dev;
 | 
						|
+
 | 
						|
+	/* Switch to SNFI mode */
 | 
						|
+	writel(SPI_MODE, tmpsnf.nfi_base + SNF_CFG);
 | 
						|
+
 | 
						|
+	/* Reset SNFI & NFI */
 | 
						|
+	mtk_snand_mac_reset(&tmpsnf);
 | 
						|
+	mtk_nfi_reset(&tmpsnf);
 | 
						|
+
 | 
						|
+	/* Reset SPI-NAND chip */
 | 
						|
+	ret = mtk_snand_chip_reset(&tmpsnf);
 | 
						|
+	if (ret) {
 | 
						|
+		snand_log_chip(dev, "Failed to reset SPI-NAND chip\n");
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	/* Probe SPI-NAND flash by JEDEC ID */
 | 
						|
+	ret = mtk_snand_id_probe(&tmpsnf, &snand_info);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	rawpage_size = snand_info->memorg.pagesize +
 | 
						|
+		       snand_info->memorg.sparesize;
 | 
						|
+
 | 
						|
+	sect_bf_size = mtk_snand_socs[pdata->soc].max_sectors *
 | 
						|
+		       sizeof(*snf->sect_bf);
 | 
						|
+
 | 
						|
+	/* Allocate memory for instance and cache */
 | 
						|
+	snf = generic_mem_alloc(dev,
 | 
						|
+				sizeof(*snf) + rawpage_size + sect_bf_size);
 | 
						|
+	if (!snf) {
 | 
						|
+		snand_log_chip(dev, "Failed to allocate memory for instance\n");
 | 
						|
+		return -ENOMEM;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	snf->sect_bf = (int *)((uintptr_t)snf + sizeof(*snf));
 | 
						|
+	snf->buf_cache = (uint8_t *)((uintptr_t)snf->sect_bf + sect_bf_size);
 | 
						|
+
 | 
						|
+	/* Allocate memory for DMA buffer */
 | 
						|
+	snf->page_cache = dma_mem_alloc(dev, rawpage_size);
 | 
						|
+	if (!snf->page_cache) {
 | 
						|
+		generic_mem_free(dev, snf);
 | 
						|
+		snand_log_chip(dev,
 | 
						|
+			       "Failed to allocate memory for DMA buffer\n");
 | 
						|
+		return -ENOMEM;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	/* Fill up instance */
 | 
						|
+	snf->pdev = dev;
 | 
						|
+	snf->nfi_base = pdata->nfi_base;
 | 
						|
+	snf->ecc_base = pdata->ecc_base;
 | 
						|
+	snf->soc = pdata->soc;
 | 
						|
+	snf->nfi_soc = &mtk_snand_socs[pdata->soc];
 | 
						|
+	snf->snfi_quad_spi = pdata->quad_spi;
 | 
						|
+
 | 
						|
+	/* Initialize SNFI & ECC engine */
 | 
						|
+	ret = mtk_snand_setup(snf, snand_info);
 | 
						|
+	if (ret) {
 | 
						|
+		dma_mem_free(dev, snf->page_cache);
 | 
						|
+		generic_mem_free(dev, snf);
 | 
						|
+		return ret;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	*psnf = snf;
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+int mtk_snand_cleanup(struct mtk_snand *snf)
 | 
						|
+{
 | 
						|
+	if (!snf)
 | 
						|
+		return 0;
 | 
						|
+
 | 
						|
+	dma_mem_free(snf->pdev, snf->page_cache);
 | 
						|
+	generic_mem_free(snf->pdev, snf);
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
--- /dev/null
 | 
						|
+++ b/drivers/mtd/mtk-snand/mtk-snand.h
 | 
						|
@@ -0,0 +1,77 @@
 | 
						|
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
 | 
						|
+/*
 | 
						|
+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
 | 
						|
+ *
 | 
						|
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
 | 
						|
+ */
 | 
						|
+
 | 
						|
+#ifndef _MTK_SNAND_H_
 | 
						|
+#define _MTK_SNAND_H_
 | 
						|
+
 | 
						|
+#ifndef PRIVATE_MTK_SNAND_HEADER
 | 
						|
+#include <stddef.h>
 | 
						|
+#include <stdint.h>
 | 
						|
+#include <stdbool.h>
 | 
						|
+#endif
 | 
						|
+
 | 
						|
+enum mtk_snand_soc {
 | 
						|
+	SNAND_SOC_MT7622,
 | 
						|
+	SNAND_SOC_MT7629,
 | 
						|
+	SNAND_SOC_MT7981,
 | 
						|
+	SNAND_SOC_MT7986,
 | 
						|
+	__SNAND_SOC_MAX
 | 
						|
+};
 | 
						|
+
 | 
						|
+struct mtk_snand_platdata {
 | 
						|
+	void *nfi_base;
 | 
						|
+	void *ecc_base;
 | 
						|
+	enum mtk_snand_soc soc;
 | 
						|
+	bool quad_spi;
 | 
						|
+};
 | 
						|
+
 | 
						|
+struct mtk_snand_chip_info {
 | 
						|
+	const char *model;
 | 
						|
+	uint64_t chipsize;
 | 
						|
+	uint32_t blocksize;
 | 
						|
+	uint32_t pagesize;
 | 
						|
+	uint32_t sparesize;
 | 
						|
+	uint32_t spare_per_sector;
 | 
						|
+	uint32_t fdm_size;
 | 
						|
+	uint32_t fdm_ecc_size;
 | 
						|
+	uint32_t num_sectors;
 | 
						|
+	uint32_t sector_size;
 | 
						|
+	uint32_t ecc_strength;
 | 
						|
+	uint32_t ecc_bytes;
 | 
						|
+};
 | 
						|
+
 | 
						|
+struct mtk_snand;
 | 
						|
+struct snand_flash_info;
 | 
						|
+
 | 
						|
+int mtk_snand_init(void *dev, const struct mtk_snand_platdata *pdata,
 | 
						|
+		   struct mtk_snand **psnf);
 | 
						|
+int mtk_snand_cleanup(struct mtk_snand *snf);
 | 
						|
+
 | 
						|
+int mtk_snand_chip_reset(struct mtk_snand *snf);
 | 
						|
+int mtk_snand_read_page(struct mtk_snand *snf, uint64_t addr, void *buf,
 | 
						|
+			void *oob, bool raw);
 | 
						|
+int mtk_snand_write_page(struct mtk_snand *snf, uint64_t addr, const void *buf,
 | 
						|
+			 const void *oob, bool raw);
 | 
						|
+int mtk_snand_erase_block(struct mtk_snand *snf, uint64_t addr);
 | 
						|
+int mtk_snand_block_isbad(struct mtk_snand *snf, uint64_t addr);
 | 
						|
+int mtk_snand_block_markbad(struct mtk_snand *snf, uint64_t addr);
 | 
						|
+int mtk_snand_fill_oob(struct mtk_snand *snf, uint8_t *oobraw,
 | 
						|
+		       const uint8_t *oobbuf, size_t ooblen);
 | 
						|
+int mtk_snand_transfer_oob(struct mtk_snand *snf, uint8_t *oobbuf,
 | 
						|
+			   size_t ooblen, const uint8_t *oobraw);
 | 
						|
+int mtk_snand_read_page_auto_oob(struct mtk_snand *snf, uint64_t addr,
 | 
						|
+				 void *buf, void *oob, size_t ooblen,
 | 
						|
+				 size_t *actualooblen, bool raw);
 | 
						|
+int mtk_snand_write_page_auto_oob(struct mtk_snand *snf, uint64_t addr,
 | 
						|
+				  const void *buf, const void *oob,
 | 
						|
+				  size_t ooblen, size_t *actualooblen,
 | 
						|
+				  bool raw);
 | 
						|
+int mtk_snand_get_chip_info(struct mtk_snand *snf,
 | 
						|
+			    struct mtk_snand_chip_info *info);
 | 
						|
+int mtk_snand_irq_process(struct mtk_snand *snf);
 | 
						|
+
 | 
						|
+#endif /* _MTK_SNAND_H_ */
 |