openwrt/package/boot/uboot-mediatek/patches/002-nand-add-spi-nand-driver.patch
Daniel Golle ed50004319 uboot-mediatek: add support for Linksys E8450
Build U-Boot for the Linksys E8450 in order to have support for UBI.
The loader has a default environment with scripts handling the reset
button as well as fall-back to recovery firmware. If the loader comes
up without a valid environment found in UBI, it will automatically
make sure UBI is formatted and create a new environment and proceed
to load recovery firmware (either from UBI or via TFTP if recovery is
corrupted or unavailable).

If the button is held down during power-on, the yellow status LED
turns on and the bootloader environment is reset to factory defaults.
If the button is released at this point, the recovery firmware (if
existing) is loaded from UBI and booted.
If the button is continously held down even beyond the point that
the yellow LED turned on, the loader will try to load the recovery
firmware via TFTP from server 192.168.1.254, write it to UBI and
boot.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
2021-02-28 01:23:48 +00:00

8541 lines
212 KiB
Diff

From de8b6cf615be20b25d0f3c817866de2c0d46a704 Mon Sep 17 00:00:00 2001
From: Sam Shih <sam.shih@mediatek.com>
Date: Mon, 20 Apr 2020 17:10:05 +0800
Subject: [PATCH 1/3] nand: add spi nand driver
Add spi nand driver support for mt7622 based on nfi controller
Signed-off-by: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
---
drivers/mtd/Kconfig | 7 +
drivers/mtd/Makefile | 4 +
drivers/mtd/nand/raw/nand.c | 2 +
drivers/mtd/nandx/NOTICE | 52 +
drivers/mtd/nandx/Nandx.config | 17 +
drivers/mtd/nandx/Nandx.mk | 91 ++
drivers/mtd/nandx/README | 31 +
drivers/mtd/nandx/core/Nandx.mk | 38 +
drivers/mtd/nandx/core/core_io.c | 735 +++++++++
drivers/mtd/nandx/core/core_io.h | 39 +
drivers/mtd/nandx/core/nand/device_spi.c | 200 +++
drivers/mtd/nandx/core/nand/device_spi.h | 132 ++
drivers/mtd/nandx/core/nand/nand_spi.c | 526 +++++++
drivers/mtd/nandx/core/nand/nand_spi.h | 35 +
drivers/mtd/nandx/core/nand_base.c | 304 ++++
drivers/mtd/nandx/core/nand_base.h | 71 +
drivers/mtd/nandx/core/nand_chip.c | 272 ++++
drivers/mtd/nandx/core/nand_chip.h | 103 ++
drivers/mtd/nandx/core/nand_device.c | 285 ++++
drivers/mtd/nandx/core/nand_device.h | 608 ++++++++
drivers/mtd/nandx/core/nfi.h | 51 +
drivers/mtd/nandx/core/nfi/nfi_base.c | 1357 +++++++++++++++++
drivers/mtd/nandx/core/nfi/nfi_base.h | 95 ++
drivers/mtd/nandx/core/nfi/nfi_regs.h | 114 ++
drivers/mtd/nandx/core/nfi/nfi_spi.c | 689 +++++++++
drivers/mtd/nandx/core/nfi/nfi_spi.h | 44 +
drivers/mtd/nandx/core/nfi/nfi_spi_regs.h | 64 +
drivers/mtd/nandx/core/nfi/nfiecc.c | 510 +++++++
drivers/mtd/nandx/core/nfi/nfiecc.h | 90 ++
drivers/mtd/nandx/core/nfi/nfiecc_regs.h | 51 +
drivers/mtd/nandx/driver/Nandx.mk | 18 +
drivers/mtd/nandx/driver/bbt/bbt.c | 408 +++++
drivers/mtd/nandx/driver/uboot/driver.c | 574 +++++++
drivers/mtd/nandx/include/Nandx.mk | 16 +
drivers/mtd/nandx/include/internal/bbt.h | 62 +
.../mtd/nandx/include/internal/nandx_core.h | 250 +++
.../mtd/nandx/include/internal/nandx_errno.h | 40 +
.../mtd/nandx/include/internal/nandx_util.h | 221 +++
drivers/mtd/nandx/include/uboot/nandx_os.h | 78 +
include/configs/mt7622.h | 25 +
40 files changed, 8309 insertions(+)
create mode 100644 drivers/mtd/nandx/NOTICE
create mode 100644 drivers/mtd/nandx/Nandx.config
create mode 100644 drivers/mtd/nandx/Nandx.mk
create mode 100644 drivers/mtd/nandx/README
create mode 100644 drivers/mtd/nandx/core/Nandx.mk
create mode 100644 drivers/mtd/nandx/core/core_io.c
create mode 100644 drivers/mtd/nandx/core/core_io.h
create mode 100644 drivers/mtd/nandx/core/nand/device_spi.c
create mode 100644 drivers/mtd/nandx/core/nand/device_spi.h
create mode 100644 drivers/mtd/nandx/core/nand/nand_spi.c
create mode 100644 drivers/mtd/nandx/core/nand/nand_spi.h
create mode 100644 drivers/mtd/nandx/core/nand_base.c
create mode 100644 drivers/mtd/nandx/core/nand_base.h
create mode 100644 drivers/mtd/nandx/core/nand_chip.c
create mode 100644 drivers/mtd/nandx/core/nand_chip.h
create mode 100644 drivers/mtd/nandx/core/nand_device.c
create mode 100644 drivers/mtd/nandx/core/nand_device.h
create mode 100644 drivers/mtd/nandx/core/nfi.h
create mode 100644 drivers/mtd/nandx/core/nfi/nfi_base.c
create mode 100644 drivers/mtd/nandx/core/nfi/nfi_base.h
create mode 100644 drivers/mtd/nandx/core/nfi/nfi_regs.h
create mode 100644 drivers/mtd/nandx/core/nfi/nfi_spi.c
create mode 100644 drivers/mtd/nandx/core/nfi/nfi_spi.h
create mode 100644 drivers/mtd/nandx/core/nfi/nfi_spi_regs.h
create mode 100644 drivers/mtd/nandx/core/nfi/nfiecc.c
create mode 100644 drivers/mtd/nandx/core/nfi/nfiecc.h
create mode 100644 drivers/mtd/nandx/core/nfi/nfiecc_regs.h
create mode 100644 drivers/mtd/nandx/driver/Nandx.mk
create mode 100644 drivers/mtd/nandx/driver/bbt/bbt.c
create mode 100644 drivers/mtd/nandx/driver/uboot/driver.c
create mode 100644 drivers/mtd/nandx/include/Nandx.mk
create mode 100644 drivers/mtd/nandx/include/internal/bbt.h
create mode 100644 drivers/mtd/nandx/include/internal/nandx_core.h
create mode 100644 drivers/mtd/nandx/include/internal/nandx_errno.h
create mode 100644 drivers/mtd/nandx/include/internal/nandx_util.h
create mode 100644 drivers/mtd/nandx/include/uboot/nandx_os.h
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -108,6 +108,13 @@ config HBMC_AM654
This is the driver for HyperBus controller on TI's AM65x and
other SoCs
+config MTK_SPI_NAND
+ tristate "Mediatek SPI Nand"
+ depends on DM_MTD
+ help
+ This option will support SPI Nand device via Mediatek
+ NFI controller.
+
source "drivers/mtd/nand/Kconfig"
source "drivers/mtd/spi/Kconfig"
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -41,3 +41,7 @@ obj-$(CONFIG_$(SPL_TPL_)SPI_FLASH_SUPPOR
obj-$(CONFIG_SPL_UBI) += ubispl/
endif
+
+ifeq ($(CONFIG_MTK_SPI_NAND), y)
+include $(srctree)/drivers/mtd/nandx/Nandx.mk
+endif
--- a/drivers/mtd/nand/raw/nand.c
+++ b/drivers/mtd/nand/raw/nand.c
@@ -91,8 +91,10 @@ static void nand_init_chip(int i)
if (board_nand_init(nand))
return;
+#ifndef CONFIG_MTK_SPI_NAND
if (nand_scan(mtd, maxchips))
return;
+#endif
nand_register(i, mtd);
}
--- /dev/null
+++ b/drivers/mtd/nandx/NOTICE
@@ -0,0 +1,52 @@
+
+/*
+ * Nandx - Mediatek Common Nand Driver
+ * Copyright (C) 2017 MediaTek Inc.
+ *
+ * Nandx is dual licensed: you can use it either under the terms of
+ * the GPL, or the BSD license, at your option.
+ *
+ * a) This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ *
+ * Alternatively,
+ *
+ * b) Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer.
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+####################################################################################################
\ No newline at end of file
--- /dev/null
+++ b/drivers/mtd/nandx/Nandx.config
@@ -0,0 +1,17 @@
+NANDX_SIMULATOR_SUPPORT := n
+NANDX_CTP_SUPPORT := n
+NANDX_DA_SUPPORT := n
+NANDX_PRELOADER_SUPPORT := n
+NANDX_LK_SUPPORT := n
+NANDX_KERNEL_SUPPORT := n
+NANDX_BROM_SUPPORT := n
+NANDX_UBOOT_SUPPORT := y
+NANDX_BBT_SUPPORT := y
+
+NANDX_NAND_SPI := y
+NANDX_NAND_SLC := n
+NANDX_NAND_MLC := n
+NANDX_NAND_TLC := n
+NANDX_NFI_BASE := y
+NANDX_NFI_ECC := y
+NANDX_NFI_SPI := y
--- /dev/null
+++ b/drivers/mtd/nandx/Nandx.mk
@@ -0,0 +1,91 @@
+#
+# Copyright (C) 2017 MediaTek Inc.
+# Licensed under either
+# BSD Licence, (see NOTICE for more details)
+# GNU General Public License, version 2.0, (see NOTICE for more details)
+#
+
+nandx_dir := $(shell dirname $(lastword $(MAKEFILE_LIST)))
+include $(nandx_dir)/Nandx.config
+
+ifeq ($(NANDX_SIMULATOR_SUPPORT), y)
+sim-obj :=
+sim-inc :=
+nandx-obj := sim-obj
+nandx-prefix := .
+nandx-postfix := %.o
+sim-inc += -I$(nandx-prefix)/include/internal
+sim-inc += -I$(nandx-prefix)/include/simulator
+endif
+
+ifeq ($(NANDX_CTP_SUPPORT), y)
+nandx-obj := C_SRC_FILES
+nandx-prefix := $(nandx_dir)
+nandx-postfix := %.c
+INC_DIRS += $(nandx_dir)/include/internal
+INC_DIRS += $(nandx_dir)/include/ctp
+endif
+
+ifeq ($(NANDX_DA_SUPPORT), y)
+nandx-obj := obj-y
+nandx-prefix := $(nandx_dir)
+nandx-postfix := %.o
+INCLUDE_PATH += $(TOPDIR)/platform/$(CODE_BASE)/dev/nand/nandx/include/internal
+INCLUDE_PATH += $(TOPDIR)/platform/$(CODE_BASE)/dev/nand/nandx/include/da
+endif
+
+ifeq ($(NANDX_PRELOADER_SUPPORT), y)
+nandx-obj := MOD_SRC
+nandx-prefix := $(nandx_dir)
+nandx-postfix := %.c
+C_OPTION += -I$(MTK_PATH_PLATFORM)/src/drivers/nandx/include/internal
+C_OPTION += -I$(MTK_PATH_PLATFORM)/src/drivers/nandx/include/preloader
+endif
+
+ifeq ($(NANDX_LK_SUPPORT), y)
+nandx-obj := MODULE_SRCS
+nandx-prefix := $(nandx_dir)
+nandx-postfix := %.c
+GLOBAL_INCLUDES += $(nandx_dir)/include/internal
+GLOBAL_INCLUDES += $(nandx_dir)/include/lk
+endif
+
+ifeq ($(NANDX_KERNEL_SUPPORT), y)
+nandx-obj := obj-y
+nandx-prefix := nandx
+nandx-postfix := %.o
+ccflags-y += -I$(nandx_dir)/include/internal
+ccflags-y += -I$(nandx_dir)/include/kernel
+endif
+
+ifeq ($(NANDX_UBOOT_SUPPORT), y)
+nandx-obj := obj-y
+nandx-prefix := nandx
+nandx-postfix := %.o
+ccflags-y += -I$(nandx_dir)/include/internal
+ccflags-y += -I$(nandx_dir)/include/uboot
+endif
+
+nandx-y :=
+include $(nandx_dir)/core/Nandx.mk
+nandx-target := $(nandx-prefix)/core/$(nandx-postfix)
+$(nandx-obj) += $(patsubst %.c, $(nandx-target), $(nandx-y))
+
+
+nandx-y :=
+include $(nandx_dir)/driver/Nandx.mk
+nandx-target := $(nandx-prefix)/driver/$(nandx-postfix)
+$(nandx-obj) += $(patsubst %.c, $(nandx-target), $(nandx-y))
+
+ifeq ($(NANDX_SIMULATOR_SUPPORT), y)
+cc := gcc
+CFLAGS += $(sim-inc)
+
+.PHONY:nandx
+nandx: $(sim-obj)
+ $(cc) $(sim-obj) -o nandx
+
+.PHONY:clean
+clean:
+ rm -rf $(sim-obj) nandx
+endif
--- /dev/null
+++ b/drivers/mtd/nandx/README
@@ -0,0 +1,31 @@
+
+ NAND2.0
+ ===============================
+
+ NAND2.0 is a common nand driver which designed for accessing
+different type of NANDs(SLC, SPI-NAND, MLC, TLC) on various OS. This
+driver can work on mostly SoCs of Mediatek.
+
+ Although there already has a common nand driver, it doesn't cover
+SPI-NAND, and not match our IC-Verification's reqirement. We need
+a driver that can be exten or cut easily.
+
+ This driver is base on NANDX & SLC. We try to refactor structures,
+and make them inheritable. We also refactor some operations' flow
+principally for adding SPI-NAND support.
+
+ This driver's architecture is like:
+
+ Driver @LK/Uboot/DA... |IC verify/other purposes
+ ----------------------------------------------------------------
+ partition | BBM |
+ -------------------------------------- | extend_core
+ nandx_core/core_io |
+ ----------------------------------------------------------------
+ nand_chip/nand_base |
+ -------------------------------------- | extend_nfi
+ nand_device | nfi/nfi_base |
+
+ Any block of above graph can be extended at your will, if you
+want add new feature into this code, please make sure that your code
+would follow the framework, and we will be appreciated about it.
--- /dev/null
+++ b/drivers/mtd/nandx/core/Nandx.mk
@@ -0,0 +1,38 @@
+#
+# Copyright (C) 2017 MediaTek Inc.
+# Licensed under either
+# BSD Licence, (see NOTICE for more details)
+# GNU General Public License, version 2.0, (see NOTICE for more details)
+#
+
+nandx-y += nand_device.c
+nandx-y += nand_base.c
+nandx-y += nand_chip.c
+nandx-y += core_io.c
+
+nandx-header-y += nand_device.h
+nandx-header-y += nand_base.h
+nandx-header-y += nand_chip.h
+nandx-header-y += core_io.h
+nandx-header-y += nfi.h
+
+nandx-$(NANDX_NAND_SPI) += nand/device_spi.c
+nandx-$(NANDX_NAND_SPI) += nand/nand_spi.c
+nandx-$(NANDX_NAND_SLC) += nand/device_slc.c
+nandx-$(NANDX_NAND_SLC) += nand/nand_slc.c
+
+nandx-header-$(NANDX_NAND_SPI) += nand/device_spi.h
+nandx-header-$(NANDX_NAND_SPI) += nand/nand_spi.h
+nandx-header-$(NANDX_NAND_SLC) += nand/device_slc.h
+nandx-header-$(NANDX_NAND_SLC) += nand/nand_slc.h
+
+nandx-$(NANDX_NFI_BASE) += nfi/nfi_base.c
+nandx-$(NANDX_NFI_ECC) += nfi/nfiecc.c
+nandx-$(NANDX_NFI_SPI) += nfi/nfi_spi.c
+
+nandx-header-$(NANDX_NFI_BASE) += nfi/nfi_base.h
+nandx-header-$(NANDX_NFI_BASE) += nfi/nfi_regs.h
+nandx-header-$(NANDX_NFI_ECC) += nfi/nfiecc.h
+nandx-header-$(NANDX_NFI_ECC) += nfi/nfiecc_regs.h
+nandx-header-$(NANDX_NFI_SPI) += nfi/nfi_spi.h
+nandx-header-$(NANDX_NFI_SPI) += nfi/nfi_spi_regs.h
--- /dev/null
+++ b/drivers/mtd/nandx/core/core_io.c
@@ -0,0 +1,735 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+/*NOTE: switch cache/multi*/
+#include "nandx_util.h"
+#include "nandx_core.h"
+#include "nand_chip.h"
+#include "core_io.h"
+
+static struct nandx_desc *g_nandx;
+
+static inline bool is_sector_align(u64 val)
+{
+ return reminder(val, g_nandx->chip->sector_size) ? false : true;
+}
+
+static inline bool is_page_align(u64 val)
+{
+ return reminder(val, g_nandx->chip->page_size) ? false : true;
+}
+
+static inline bool is_block_align(u64 val)
+{
+ return reminder(val, g_nandx->chip->block_size) ? false : true;
+}
+
+static inline u32 page_sectors(void)
+{
+ return div_down(g_nandx->chip->page_size, g_nandx->chip->sector_size);
+}
+
+static inline u32 sector_oob(void)
+{
+ return div_down(g_nandx->chip->oob_size, page_sectors());
+}
+
+static inline u32 sector_padded_size(void)
+{
+ return g_nandx->chip->sector_size + g_nandx->chip->sector_spare_size;
+}
+
+static inline u32 page_padded_size(void)
+{
+ return page_sectors() * sector_padded_size();
+}
+
+static inline u32 offset_to_padded_col(u64 offset)
+{
+ struct nandx_desc *nandx = g_nandx;
+ u32 col, sectors;
+
+ col = reminder(offset, nandx->chip->page_size);
+ sectors = div_down(col, nandx->chip->sector_size);
+
+ return col + sectors * nandx->chip->sector_spare_size;
+}
+
+static inline u32 offset_to_row(u64 offset)
+{
+ return div_down(offset, g_nandx->chip->page_size);
+}
+
+static inline u32 offset_to_col(u64 offset)
+{
+ return reminder(offset, g_nandx->chip->page_size);
+}
+
+static inline u32 oob_upper_size(void)
+{
+ return g_nandx->ecc_en ? g_nandx->chip->oob_size :
+ g_nandx->chip->sector_spare_size * page_sectors();
+}
+
+static inline bool is_upper_oob_align(u64 val)
+{
+ return reminder(val, oob_upper_size()) ? false : true;
+}
+
+#define prepare_op(_op, _row, _col, _len, _data, _oob) \
+ do { \
+ (_op).row = (_row); \
+ (_op).col = (_col); \
+ (_op).len = (_len); \
+ (_op).data = (_data); \
+ (_op).oob = (_oob); \
+ } while (0)
+
+static int operation_multi(enum nandx_op_mode mode, u8 *data, u8 *oob,
+ u64 offset, size_t len)
+{
+ struct nandx_desc *nandx = g_nandx;
+ u32 row = offset_to_row(offset);
+ u32 col = offset_to_padded_col(offset);
+
+ if (nandx->mode == NANDX_IDLE) {
+ nandx->mode = mode;
+ nandx->ops_current = 0;
+ } else if (nandx->mode != mode) {
+ pr_info("forbid mixed operations.\n");
+ return -EOPNOTSUPP;
+ }
+
+ prepare_op(nandx->ops[nandx->ops_current], row, col, len, data, oob);
+ nandx->ops_current++;
+
+ if (nandx->ops_current == nandx->ops_multi_len)
+ return nandx_sync();
+
+ return nandx->ops_multi_len - nandx->ops_current;
+}
+
+static int operation_sequent(enum nandx_op_mode mode, u8 *data, u8 *oob,
+ u64 offset, size_t len)
+{
+ struct nandx_desc *nandx = g_nandx;
+ struct nand_chip *chip = nandx->chip;
+ u32 row = offset_to_row(offset);
+ func_chip_ops chip_ops;
+ u8 *ref_data = data, *ref_oob = oob;
+ int align, ops, row_step;
+ int i, rem;
+
+ align = data ? chip->page_size : oob_upper_size();
+ ops = data ? div_down(len, align) : div_down(len, oob_upper_size());
+ row_step = 1;
+
+ switch (mode) {
+ case NANDX_ERASE:
+ chip_ops = chip->erase_block;
+ align = chip->block_size;
+ ops = div_down(len, align);
+ row_step = chip->block_pages;
+ break;
+
+ case NANDX_READ:
+ chip_ops = chip->read_page;
+ break;
+
+ case NANDX_WRITE:
+ chip_ops = chip->write_page;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (!data) {
+ ref_data = nandx->head_buf;
+ memset(ref_data, 0xff, chip->page_size);
+ }
+
+ if (!oob) {
+ ref_oob = nandx->head_buf + chip->page_size;
+ memset(ref_oob, 0xff, oob_upper_size());
+ }
+
+ for (i = 0; i < ops; i++) {
+ prepare_op(nandx->ops[nandx->ops_current],
+ row + i * row_step, 0, align, ref_data, ref_oob);
+ nandx->ops_current++;
+ /* if data or oob is null, nandx->head_buf or
+ * nandx->head_buf + chip->page_size should not been used
+ * so, here it is safe to use the buf.
+ */
+ ref_data = data ? ref_data + chip->page_size : nandx->head_buf;
+ ref_oob = oob ? ref_oob + oob_upper_size() :
+ nandx->head_buf + chip->page_size;
+ }
+
+ if (nandx->mode == NANDX_WRITE) {
+ rem = reminder(nandx->ops_current, nandx->min_write_pages);
+ if (rem)
+ return nandx->min_write_pages - rem;
+ }
+
+ nandx->ops_current = 0;
+ return chip_ops(chip, nandx->ops, ops);
+}
+
+static int read_pages(u8 *data, u8 *oob, u64 offset, size_t len)
+{
+ struct nandx_desc *nandx = g_nandx;
+ struct nand_chip *chip = nandx->chip;
+ struct nandx_split64 split = {0};
+ u8 *ref_data = data, *ref_oob;
+ u32 row, col;
+ int ret = 0, i, ops;
+ u32 head_offset = 0;
+ u64 val;
+
+ if (!data)
+ return operation_sequent(NANDX_READ, NULL, oob, offset, len);
+
+ ref_oob = oob ? oob : nandx->head_buf + chip->page_size;
+
+ nandx_split(&split, offset, len, val, chip->page_size);
+
+ if (split.head_len) {
+ row = offset_to_row(split.head);
+ col = offset_to_col(split.head);
+ prepare_op(nandx->ops[nandx->ops_current], row, 0,
+ chip->page_size,
+ nandx->head_buf, ref_oob);
+ nandx->ops_current++;
+
+ head_offset = col;
+
+ ref_data += split.head_len;
+ ref_oob = oob ? ref_oob + oob_upper_size() :
+ nandx->head_buf + chip->page_size;
+ }
+
+ if (split.body_len) {
+ ops = div_down(split.body_len, chip->page_size);
+ row = offset_to_row(split.body);
+ for (i = 0; i < ops; i++) {
+ prepare_op(nandx->ops[nandx->ops_current],
+ row + i, 0, chip->page_size,
+ ref_data, ref_oob);
+ nandx->ops_current++;
+ ref_data += chip->page_size;
+ ref_oob = oob ? ref_oob + oob_upper_size() :
+ nandx->head_buf + chip->page_size;
+ }
+ }
+
+ if (split.tail_len) {
+ row = offset_to_row(split.tail);
+ prepare_op(nandx->ops[nandx->ops_current], row, 0,
+ chip->page_size, nandx->tail_buf, ref_oob);
+ nandx->ops_current++;
+ }
+
+ ret = chip->read_page(chip, nandx->ops, nandx->ops_current);
+
+ if (split.head_len)
+ memcpy(data, nandx->head_buf + head_offset, split.head_len);
+ if (split.tail_len)
+ memcpy(ref_data, nandx->tail_buf, split.tail_len);
+
+ nandx->ops_current = 0;
+ return ret;
+}
+
+int nandx_read(u8 *data, u8 *oob, u64 offset, size_t len)
+{
+ struct nandx_desc *nandx = g_nandx;
+
+ if (!len || len > nandx->info.total_size)
+ return -EINVAL;
+ if (div_up(len, nandx->chip->page_size) > nandx->ops_len)
+ return -EINVAL;
+ if (!data && !oob)
+ return -EINVAL;
+ /**
+ * as design, oob not support partial read
+ * and, the length of oob buf should be oob size aligned
+ */
+ if (!data && !is_upper_oob_align(len))
+ return -EINVAL;
+
+ if (g_nandx->multi_en) {
+ /* as design, there only 2 buf for partial read,
+ * if partial read allowed for multi read,
+ * there are not enough buf
+ */
+ if (!is_sector_align(offset))
+ return -EINVAL;
+ if (data && !is_sector_align(len))
+ return -EINVAL;
+ return operation_multi(NANDX_READ, data, oob, offset, len);
+ }
+
+ nandx->ops_current = 0;
+ nandx->mode = NANDX_IDLE;
+ return read_pages(data, oob, offset, len);
+}
+
+static int write_pages(u8 *data, u8 *oob, u64 offset, size_t len)
+{
+ struct nandx_desc *nandx = g_nandx;
+ struct nand_chip *chip = nandx->chip;
+ struct nandx_split64 split = {0};
+ int ret, rem, i, ops;
+ u32 row, col;
+ u8 *ref_oob = oob;
+ u64 val;
+
+ nandx->mode = NANDX_WRITE;
+
+ if (!data)
+ return operation_sequent(NANDX_WRITE, NULL, oob, offset, len);
+
+ if (!oob) {
+ ref_oob = nandx->head_buf + chip->page_size;
+ memset(ref_oob, 0xff, oob_upper_size());
+ }
+
+ nandx_split(&split, offset, len, val, chip->page_size);
+
+ /*NOTE: slc can support sector write, here copy too many data.*/
+ if (split.head_len) {
+ row = offset_to_row(split.head);
+ col = offset_to_col(split.head);
+ memset(nandx->head_buf, 0xff, page_padded_size());
+ memcpy(nandx->head_buf + col, data, split.head_len);
+ prepare_op(nandx->ops[nandx->ops_current], row, 0,
+ chip->page_size, nandx->head_buf, ref_oob);
+ nandx->ops_current++;
+
+ data += split.head_len;
+ ref_oob = oob ? ref_oob + oob_upper_size() :
+ nandx->head_buf + chip->page_size;
+ }
+
+ if (split.body_len) {
+ row = offset_to_row(split.body);
+ ops = div_down(split.body_len, chip->page_size);
+ for (i = 0; i < ops; i++) {
+ prepare_op(nandx->ops[nandx->ops_current],
+ row + i, 0, chip->page_size, data, ref_oob);
+ nandx->ops_current++;
+ data += chip->page_size;
+ ref_oob = oob ? ref_oob + oob_upper_size() :
+ nandx->head_buf + chip->page_size;
+ }
+ }
+
+ if (split.tail_len) {
+ row = offset_to_row(split.tail);
+ memset(nandx->tail_buf, 0xff, page_padded_size());
+ memcpy(nandx->tail_buf, data, split.tail_len);
+ prepare_op(nandx->ops[nandx->ops_current], row, 0,
+ chip->page_size, nandx->tail_buf, ref_oob);
+ nandx->ops_current++;
+ }
+
+ rem = reminder(nandx->ops_current, nandx->min_write_pages);
+ if (rem)
+ return nandx->min_write_pages - rem;
+
+ ret = chip->write_page(chip, nandx->ops, nandx->ops_current);
+
+ nandx->ops_current = 0;
+ nandx->mode = NANDX_IDLE;
+ return ret;
+}
+
+int nandx_write(u8 *data, u8 *oob, u64 offset, size_t len)
+{
+ struct nandx_desc *nandx = g_nandx;
+
+ if (!len || len > nandx->info.total_size)
+ return -EINVAL;
+ if (div_up(len, nandx->chip->page_size) > nandx->ops_len)
+ return -EINVAL;
+ if (!data && !oob)
+ return -EINVAL;
+ if (!data && !is_upper_oob_align(len))
+ return -EINVAL;
+
+ if (nandx->multi_en) {
+ if (!is_page_align(offset))
+ return -EINVAL;
+ if (data && !is_page_align(len))
+ return -EINVAL;
+
+ return operation_multi(NANDX_WRITE, data, oob, offset, len);
+ }
+
+ return write_pages(data, oob, offset, len);
+}
+
+int nandx_erase(u64 offset, size_t len)
+{
+ struct nandx_desc *nandx = g_nandx;
+
+ if (!len || len > nandx->info.total_size)
+ return -EINVAL;
+ if (div_down(len, nandx->chip->block_size) > nandx->ops_len)
+ return -EINVAL;
+ if (!is_block_align(offset) || !is_block_align(len))
+ return -EINVAL;
+
+ if (g_nandx->multi_en)
+ return operation_multi(NANDX_ERASE, NULL, NULL, offset, len);
+
+ nandx->ops_current = 0;
+ nandx->mode = NANDX_IDLE;
+ return operation_sequent(NANDX_ERASE, NULL, NULL, offset, len);
+}
+
+int nandx_sync(void)
+{
+ struct nandx_desc *nandx = g_nandx;
+ struct nand_chip *chip = nandx->chip;
+ func_chip_ops chip_ops;
+ int ret, i, rem;
+
+ if (!nandx->ops_current)
+ return 0;
+
+ rem = reminder(nandx->ops_current, nandx->ops_multi_len);
+ if (nandx->multi_en && rem) {
+ ret = -EIO;
+ goto error;
+ }
+
+ switch (nandx->mode) {
+ case NANDX_IDLE:
+ return 0;
+ case NANDX_ERASE:
+ chip_ops = chip->erase_block;
+ break;
+ case NANDX_READ:
+ chip_ops = chip->read_page;
+ break;
+ case NANDX_WRITE:
+ chip_ops = chip->write_page;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ rem = reminder(nandx->ops_current, nandx->min_write_pages);
+ if (!nandx->multi_en && nandx->mode == NANDX_WRITE && rem) {
+ /* in one process of program, only allow 2 pages to do partial
+ * write, here we supposed 1st buf would be used, and 2nd
+ * buf should be not used.
+ */
+ memset(nandx->tail_buf, 0xff,
+ chip->page_size + oob_upper_size());
+ for (i = 0; i < rem; i++) {
+ prepare_op(nandx->ops[nandx->ops_current],
+ nandx->ops[nandx->ops_current - 1].row + 1,
+ 0, chip->page_size, nandx->tail_buf,
+ nandx->tail_buf + chip->page_size);
+ nandx->ops_current++;
+ }
+ }
+
+ ret = chip_ops(nandx->chip, nandx->ops, nandx->ops_current);
+
+error:
+ nandx->mode = NANDX_IDLE;
+ nandx->ops_current = 0;
+
+ return ret;
+}
+
+int nandx_ioctl(int cmd, void *arg)
+{
+ struct nandx_desc *nandx = g_nandx;
+ struct nand_chip *chip = nandx->chip;
+ int ret = 0;
+
+ switch (cmd) {
+ case CORE_CTRL_NAND_INFO:
+ *(struct nandx_info *)arg = nandx->info;
+ break;
+
+ case CHIP_CTRL_OPS_MULTI:
+ ret = chip->chip_ctrl(chip, cmd, arg);
+ if (!ret)
+ nandx->multi_en = *(bool *)arg;
+ break;
+
+ case NFI_CTRL_ECC:
+ ret = chip->chip_ctrl(chip, cmd, arg);
+ if (!ret)
+ nandx->ecc_en = *(bool *)arg;
+ break;
+
+ default:
+ ret = chip->chip_ctrl(chip, cmd, arg);
+ break;
+ }
+
+ return ret;
+}
+
+bool nandx_is_bad_block(u64 offset)
+{
+ struct nandx_desc *nandx = g_nandx;
+
+ prepare_op(nandx->ops[0], offset_to_row(offset), 0,
+ nandx->chip->page_size, nandx->head_buf,
+ nandx->head_buf + nandx->chip->page_size);
+
+ return nandx->chip->is_bad_block(nandx->chip, nandx->ops, 1);
+}
+
+int nandx_suspend(void)
+{
+ return g_nandx->chip->suspend(g_nandx->chip);
+}
+
+int nandx_resume(void)
+{
+ return g_nandx->chip->resume(g_nandx->chip);
+}
+
+int nandx_init(struct nfi_resource *res)
+{
+ struct nand_chip *chip;
+ struct nandx_desc *nandx;
+ int ret = 0;
+
+ if (!res)
+ return -EINVAL;
+
+ chip = nand_chip_init(res);
+ if (!chip) {
+ pr_info("nand chip init fail.\n");
+ return -EFAULT;
+ }
+
+ nandx = (struct nandx_desc *)mem_alloc(1, sizeof(struct nandx_desc));
+ if (!nandx)
+ return -ENOMEM;
+
+ g_nandx = nandx;
+
+ nandx->chip = chip;
+ nandx->min_write_pages = chip->min_program_pages;
+ nandx->ops_multi_len = nandx->min_write_pages * chip->plane_num;
+ nandx->ops_len = chip->block_pages * chip->plane_num;
+ nandx->ops = mem_alloc(1, sizeof(struct nand_ops) * nandx->ops_len);
+ if (!nandx->ops) {
+ ret = -ENOMEM;
+ goto ops_error;
+ }
+
+#if NANDX_BULK_IO_USE_DRAM
+ nandx->head_buf = NANDX_CORE_BUF_ADDR;
+#else
+ nandx->head_buf = mem_alloc(2, page_padded_size());
+#endif
+ if (!nandx->head_buf) {
+ ret = -ENOMEM;
+ goto buf_error;
+ }
+ nandx->tail_buf = nandx->head_buf + page_padded_size();
+ memset(nandx->head_buf, 0xff, 2 * page_padded_size());
+ nandx->multi_en = false;
+ nandx->ecc_en = false;
+ nandx->ops_current = 0;
+ nandx->mode = NANDX_IDLE;
+
+ nandx->info.max_io_count = nandx->ops_len;
+ nandx->info.min_write_pages = nandx->min_write_pages;
+ nandx->info.plane_num = chip->plane_num;
+ nandx->info.oob_size = chip->oob_size;
+ nandx->info.page_parity_size = chip->sector_spare_size * page_sectors();
+ nandx->info.page_size = chip->page_size;
+ nandx->info.block_size = chip->block_size;
+ nandx->info.total_size = chip->block_size * chip->block_num;
+ nandx->info.fdm_ecc_size = chip->fdm_ecc_size;
+ nandx->info.fdm_reg_size = chip->fdm_reg_size;
+ nandx->info.ecc_strength = chip->ecc_strength;
+ nandx->info.sector_size = chip->sector_size;
+
+ return 0;
+
+buf_error:
+#if !NANDX_BULK_IO_USE_DRAM
+ mem_free(nandx->head_buf);
+#endif
+ops_error:
+ mem_free(nandx);
+
+ return ret;
+}
+
+void nandx_exit(void)
+{
+ nand_chip_exit(g_nandx->chip);
+#if !NANDX_BULK_IO_USE_DRAM
+ mem_free(g_nandx->head_buf);
+#endif
+ mem_free(g_nandx->ops);
+ mem_free(g_nandx);
+}
+
+#ifdef NANDX_UNIT_TEST
+static void dump_buf(u8 *buf, u32 len)
+{
+ u32 i;
+
+ pr_info("dump buf@0x%X start", (u32)buf);
+ for (i = 0; i < len; i++) {
+ if (!reminder(i, 16))
+ pr_info("\n0x");
+ pr_info("%x ", buf[i]);
+ }
+ pr_info("\ndump buf done.\n");
+}
+
+int nandx_unit_test(u64 offset, size_t len)
+{
+ u8 *src_buf, *dst_buf;
+ u32 i, j;
+ int ret;
+
+ if (!len || len > g_nandx->chip->block_size)
+ return -EINVAL;
+
+#if NANDX_BULK_IO_USE_DRAM
+ src_buf = NANDX_UT_SRC_ADDR;
+ dst_buf = NANDX_UT_DST_ADDR;
+
+#else
+ src_buf = mem_alloc(1, g_nandx->chip->page_size);
+ if (!src_buf)
+ return -ENOMEM;
+ dst_buf = mem_alloc(1, g_nandx->chip->page_size);
+ if (!dst_buf) {
+ mem_free(src_buf);
+ return -ENOMEM;
+ }
+#endif
+
+ pr_info("%s: src_buf address 0x%x, dst_buf address 0x%x\n",
+ __func__, (int)((unsigned long)src_buf),
+ (int)((unsigned long)dst_buf));
+
+ memset(dst_buf, 0, g_nandx->chip->page_size);
+ pr_info("read page 0 data...!\n");
+ ret = nandx_read(dst_buf, NULL, 0, g_nandx->chip->page_size);
+ if (ret < 0) {
+ pr_info("read fail with ret %d\n", ret);
+ } else {
+ pr_info("read page success!\n");
+ }
+
+ for (i = 0; i < g_nandx->chip->page_size; i++) {
+ src_buf[i] = 0x5a;
+ }
+
+ ret = nandx_erase(offset, g_nandx->chip->block_size);
+ if (ret < 0) {
+ pr_info("erase fail with ret %d\n", ret);
+ goto error;
+ }
+
+ for (j = 0; j < g_nandx->chip->block_pages; j++) {
+ memset(dst_buf, 0, g_nandx->chip->page_size);
+ pr_info("check data after erase...!\n");
+ ret = nandx_read(dst_buf, NULL, offset, g_nandx->chip->page_size);
+ if (ret < 0) {
+ pr_info("read fail with ret %d\n", ret);
+ goto error;
+ }
+
+ for (i = 0; i < g_nandx->chip->page_size; i++) {
+ if (dst_buf[i] != 0xff) {
+ pr_info("read after erase, check fail @%d\n", i);
+ pr_info("all data should be 0xff\n");
+ ret = -ENANDERASE;
+ dump_buf(dst_buf, 128);
+ //goto error;
+ break;
+ }
+ }
+
+ pr_info("write data...!\n");
+ ret = nandx_write(src_buf, NULL, offset, g_nandx->chip->page_size);
+ if (ret < 0) {
+ pr_info("write fail with ret %d\n", ret);
+ goto error;
+ }
+
+ memset(dst_buf, 0, g_nandx->chip->page_size);
+ pr_info("read data...!\n");
+ ret = nandx_read(dst_buf, NULL, offset, g_nandx->chip->page_size);
+ if (ret < 0) {
+ pr_info("read fail with ret %d\n", ret);
+ goto error;
+ }
+
+ for (i = 0; i < g_nandx->chip->page_size; i++) {
+ if (dst_buf[i] != src_buf[i]) {
+ pr_info("read after write, check fail @%d\n", i);
+ pr_info("dst_buf should be same as src_buf\n");
+ ret = -EIO;
+ dump_buf(src_buf + i, 128);
+ dump_buf(dst_buf + i, 128);
+ break;
+ }
+ }
+
+ pr_err("%s %d %s@%d\n", __func__, __LINE__, ret?"Failed":"OK", j);
+ if (ret)
+ break;
+
+ offset += g_nandx->chip->page_size;
+ }
+
+ ret = nandx_erase(offset, g_nandx->chip->block_size);
+ if (ret < 0) {
+ pr_info("erase fail with ret %d\n", ret);
+ goto error;
+ }
+
+ memset(dst_buf, 0, g_nandx->chip->page_size);
+ ret = nandx_read(dst_buf, NULL, offset, g_nandx->chip->page_size);
+ if (ret < 0) {
+ pr_info("read fail with ret %d\n", ret);
+ goto error;
+ }
+
+ for (i = 0; i < g_nandx->chip->page_size; i++) {
+ if (dst_buf[i] != 0xff) {
+ pr_info("read after erase, check fail\n");
+ pr_info("all data should be 0xff\n");
+ ret = -ENANDERASE;
+ dump_buf(dst_buf, 128);
+ goto error;
+ }
+ }
+
+ return 0;
+
+error:
+#if !NANDX_BULK_IO_USE_DRAM
+ mem_free(src_buf);
+ mem_free(dst_buf);
+#endif
+ return ret;
+}
+#endif
--- /dev/null
+++ b/drivers/mtd/nandx/core/core_io.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __CORE_IO_H__
+#define __CORE_IO_H__
+
+typedef int (*func_chip_ops)(struct nand_chip *, struct nand_ops *,
+ int);
+
+enum nandx_op_mode {
+ NANDX_IDLE,
+ NANDX_WRITE,
+ NANDX_READ,
+ NANDX_ERASE
+};
+
+struct nandx_desc {
+ struct nand_chip *chip;
+ struct nandx_info info;
+ enum nandx_op_mode mode;
+
+ bool multi_en;
+ bool ecc_en;
+
+ struct nand_ops *ops;
+ int ops_len;
+ int ops_multi_len;
+ int ops_current;
+ int min_write_pages;
+
+ u8 *head_buf;
+ u8 *tail_buf;
+};
+
+#endif /* __CORE_IO_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/core/nand/device_spi.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#include "nandx_util.h"
+#include "../nand_device.h"
+#include "device_spi.h"
+
+/* spi nand basic commands */
+static struct nand_cmds spi_cmds = {
+ .reset = 0xff,
+ .read_id = 0x9f,
+ .read_status = 0x0f,
+ .read_param_page = 0x03,
+ .set_feature = 0x1f,
+ .get_feature = 0x0f,
+ .read_1st = 0x13,
+ .read_2nd = -1,
+ .random_out_1st = 0x03,
+ .random_out_2nd = -1,
+ .program_1st = 0x02,
+ .program_2nd = 0x10,
+ .erase_1st = 0xd8,
+ .erase_2nd = -1,
+ .read_cache = 0x30,
+ .read_cache_last = 0x3f,
+ .program_cache = 0x02
+};
+
+/* spi nand extend commands */
+static struct spi_extend_cmds spi_extend_cmds = {
+ .die_select = 0xc2,
+ .write_enable = 0x06
+};
+
+/* means the start bit of addressing type */
+static struct nand_addressing spi_addressing = {
+ .row_bit_start = 0,
+ .block_bit_start = 0,
+ .plane_bit_start = 12,
+ .lun_bit_start = 0,
+};
+
+/* spi nand endurance */
+static struct nand_endurance spi_endurance = {
+ .pe_cycle = 100000,
+ .ecc_req = 1,
+ .max_bitflips = 1
+};
+
+/* array_busy, write_protect, erase_fail, program_fail */
+static struct nand_status spi_status[] = {
+ {.array_busy = BIT(0),
+ .write_protect = BIT(1),
+ .erase_fail = BIT(2),
+ .program_fail = BIT(3)}
+};
+
+/* measure time by the us */
+static struct nand_array_timing spi_array_timing = {
+ .tRST = 500,
+ .tWHR = 1,
+ .tR = 25,
+ .tRCBSY = 25,
+ .tFEAT = 1,
+ .tPROG = 600,
+ .tPCBSY = 600,
+ .tBERS = 10000,
+ .tDBSY = 1
+};
+
+/* spi nand device table */
+static struct device_spi spi_nand[] = {
+ {
+ NAND_DEVICE("W25N01GV",
+ NAND_PACK_ID(0xef, 0xaa, 0x21, 0, 0, 0, 0, 0),
+ 3, 0, 3, 3,
+ 1, 1, 1, 1024, KB(128), KB(2), 64, 1,
+ &spi_cmds, &spi_addressing, &spi_status[0],
+ &spi_endurance, &spi_array_timing),
+ {
+ NAND_SPI_PROTECT(0xa0, 1, 2, 6),
+ NAND_SPI_CONFIG(0xb0, 4, 6, 0),
+ NAND_SPI_STATUS(0xc0, 4, 5),
+ NAND_SPI_CHARACTER(0xff, 0xff, 0xff, 0xff)
+ },
+ &spi_extend_cmds, 0xff, 0xff
+ },
+ {
+ NAND_DEVICE("MX35LF1G",
+ NAND_PACK_ID(0xc2, 0x12, 0x21, 0, 0, 0, 0, 0),
+ 2, 0, 3, 3,
+ 1, 1, 1, 1024, KB(128), KB(2), 64, 1,
+ &spi_cmds, &spi_addressing, &spi_status[0],
+ &spi_endurance, &spi_array_timing),
+ {
+ NAND_SPI_PROTECT(0xa0, 1, 2, 6),
+ NAND_SPI_CONFIG(0xb0, 4, 6, 1),
+ NAND_SPI_STATUS(0xc0, 4, 5),
+ NAND_SPI_CHARACTER(0xff, 0xff, 0xff, 0xff)
+ },
+ &spi_extend_cmds, 0xff, 0xff
+ },
+ {
+ NAND_DEVICE("MT29F4G01ABAFDWB",
+ NAND_PACK_ID(0x2c, 0x34, 0, 0, 0, 0, 0, 0),
+ 2, 0, 3, 3,
+ 1, 1, 1, 2048, KB(256), KB(4), 256, 1,
+ &spi_cmds, &spi_addressing, &spi_status[0],
+ &spi_endurance, &spi_array_timing),
+ {
+ NAND_SPI_PROTECT(0xa0, 1, 2, 6),
+ NAND_SPI_CONFIG(0xb0, 4, 6, 1),
+ NAND_SPI_STATUS(0xc0, 4, 5),
+ NAND_SPI_CHARACTER(0xff, 0xff, 0xff, 0xff)
+ },
+ &spi_extend_cmds, 0xff, 0xff
+ },
+ {
+ NAND_DEVICE("GD5F4GQ4UB",
+ NAND_PACK_ID(0xc8, 0xd4, 0, 0, 0, 0, 0, 0),
+ 2, 0, 3, 3,
+ 1, 1, 1, 2048, KB(256), KB(4), 256, 1,
+ &spi_cmds, &spi_addressing, &spi_status[0],
+ &spi_endurance, &spi_array_timing),
+ {
+ NAND_SPI_PROTECT(0xa0, 1, 2, 6),
+ NAND_SPI_CONFIG(0xb0, 4, 6, 1),
+ NAND_SPI_STATUS(0xc0, 4, 5),
+ NAND_SPI_CHARACTER(0xff, 0xff, 0xff, 0xff)
+ },
+ &spi_extend_cmds, 0xff, 0xff
+ },
+ {
+ NAND_DEVICE("TC58CVG2S0HRAIJ",
+ NAND_PACK_ID(0x98, 0xED, 0x51, 0, 0, 0, 0, 0),
+ 3, 0, 3, 3,
+ 1, 1, 1, 2048, KB(256), KB(4), 256, 1,
+ &spi_cmds, &spi_addressing, &spi_status[0],
+ &spi_endurance, &spi_array_timing),
+ {
+ NAND_SPI_PROTECT(0xa0, 1, 2, 6),
+ NAND_SPI_CONFIG(0xb0, 4, 6, 1),
+ NAND_SPI_STATUS(0xc0, 4, 5),
+ NAND_SPI_CHARACTER(0xff, 0xff, 0xff, 0xff)
+ },
+ &spi_extend_cmds, 0xff, 0xff
+ },
+ {
+ NAND_DEVICE("NO-DEVICE",
+ NAND_PACK_ID(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1,
+ &spi_cmds, &spi_addressing, &spi_status[0],
+ &spi_endurance, &spi_array_timing),
+ {
+ NAND_SPI_PROTECT(0xa0, 1, 2, 6),
+ NAND_SPI_CONFIG(0xb0, 4, 6, 0),
+ NAND_SPI_STATUS(0xc0, 4, 5),
+ NAND_SPI_CHARACTER(0xff, 0xff, 0xff, 0xff)
+ },
+ &spi_extend_cmds, 0xff, 0xff
+ }
+};
+
+u8 spi_replace_rx_cmds(u8 mode)
+{
+ u8 rx_replace_cmds[] = {0x03, 0x3b, 0x6b, 0xbb, 0xeb};
+
+ return rx_replace_cmds[mode];
+}
+
+u8 spi_replace_tx_cmds(u8 mode)
+{
+ u8 tx_replace_cmds[] = {0x02, 0x32};
+
+ return tx_replace_cmds[mode];
+}
+
+u8 spi_replace_rx_col_cycle(u8 mode)
+{
+ u8 rx_replace_col_cycle[] = {3, 3, 3, 3, 4};
+
+ return rx_replace_col_cycle[mode];
+}
+
+u8 spi_replace_tx_col_cycle(u8 mode)
+{
+ u8 tx_replace_col_cycle[] = {2, 2};
+
+ return tx_replace_col_cycle[mode];
+}
+
+struct nand_device *nand_get_device(int index)
+{
+ return &spi_nand[index].dev;
+}
+
--- /dev/null
+++ b/drivers/mtd/nandx/core/nand/device_spi.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __DEVICE_SPI_H__
+#define __DEVICE_SPI_H__
+
+/*
+ * extend commands
+ * @die_select: select nand device die command
+ * @write_enable: enable write command before write data to spi nand
+ * spi nand device will auto to be disable after write done
+ */
+struct spi_extend_cmds {
+ short die_select;
+ short write_enable;
+};
+
+/*
+ * protection feature register
+ * @addr: register address
+ * @wp_en_bit: write protection enable bit
+ * @bp_start_bit: block protection mask start bit
+ * @bp_end_bit: block protection mask end bit
+ */
+struct feature_protect {
+ u8 addr;
+ u8 wp_en_bit;
+ u8 bp_start_bit;
+ u8 bp_end_bit;
+};
+
+/*
+ * configuration feature register
+ * @addr: register address
+ * @ecc_en_bit: in-die ecc enable bit
+ * @otp_en_bit: enter otp access mode bit
+ * @need_qe: quad io enable bit
+ */
+struct feature_config {
+ u8 addr;
+ u8 ecc_en_bit;
+ u8 otp_en_bit;
+ u8 need_qe;
+};
+
+/*
+ * status feature register
+ * @addr: register address
+ * @ecc_start_bit: ecc status mask start bit for error bits number
+ * @ecc_end_bit: ecc status mask end bit for error bits number
+ * note that:
+ * operations status (ex. array busy status) could see on struct nand_status
+ */
+struct feature_status {
+ u8 addr;
+ u8 ecc_start_bit;
+ u8 ecc_end_bit;
+};
+
+/*
+ * character feature register
+ * @addr: register address
+ * @die_sel_bit: die select bit
+ * @drive_start_bit: drive strength mask start bit
+ * @drive_end_bit: drive strength mask end bit
+ */
+struct feature_character {
+ u8 addr;
+ u8 die_sel_bit;
+ u8 drive_start_bit;
+ u8 drive_end_bit;
+};
+
+/*
+ * spi features
+ * @protect: protection feature register
+ * @config: configuration feature register
+ * @status: status feature register
+ * @character: character feature register
+ */
+struct spi_features {
+ struct feature_protect protect;
+ struct feature_config config;
+ struct feature_status status;
+ struct feature_character character;
+};
+
+/*
+ * device_spi
+ * configurations of spi nand device table
+ * @dev: base information of nand device
+ * @feature: feature information for spi nand
+ * @extend_cmds: extended the nand base commands
+ * @tx_mode_mask: tx mode mask for chip read
+ * @rx_mode_mask: rx mode mask for chip write
+ */
+struct device_spi {
+ struct nand_device dev;
+ struct spi_features feature;
+ struct spi_extend_cmds *extend_cmds;
+
+ u8 tx_mode_mask;
+ u8 rx_mode_mask;
+};
+
+#define NAND_SPI_PROTECT(addr, wp_en_bit, bp_start_bit, bp_end_bit) \
+ {addr, wp_en_bit, bp_start_bit, bp_end_bit}
+
+#define NAND_SPI_CONFIG(addr, ecc_en_bit, otp_en_bit, need_qe) \
+ {addr, ecc_en_bit, otp_en_bit, need_qe}
+
+#define NAND_SPI_STATUS(addr, ecc_start_bit, ecc_end_bit) \
+ {addr, ecc_start_bit, ecc_end_bit}
+
+#define NAND_SPI_CHARACTER(addr, die_sel_bit, drive_start_bit, drive_end_bit) \
+ {addr, die_sel_bit, drive_start_bit, drive_end_bit}
+
+static inline struct device_spi *device_to_spi(struct nand_device *dev)
+{
+ return container_of(dev, struct device_spi, dev);
+}
+
+u8 spi_replace_rx_cmds(u8 mode);
+u8 spi_replace_tx_cmds(u8 mode);
+u8 spi_replace_rx_col_cycle(u8 mode);
+u8 spi_replace_tx_col_cycle(u8 mode);
+
+#endif /* __DEVICE_SPI_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/core/nand/nand_spi.c
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#include "nandx_util.h"
+#include "nandx_core.h"
+#include "../nand_chip.h"
+#include "../nand_device.h"
+#include "../nfi.h"
+#include "../nand_base.h"
+#include "device_spi.h"
+#include "nand_spi.h"
+
+#define READY_TIMEOUT 500000 /* us */
+
+static int nand_spi_read_status(struct nand_base *nand)
+{
+ struct device_spi *dev = device_to_spi(nand->dev);
+ u8 status;
+
+ nand->get_feature(nand, dev->feature.status.addr, &status, 1);
+
+ return status;
+}
+
+static int nand_spi_wait_ready(struct nand_base *nand, u32 timeout)
+{
+ u64 now, end;
+ int status;
+
+ end = get_current_time_us() + timeout;
+
+ do {
+ status = nand_spi_read_status(nand);
+ status &= nand->dev->status->array_busy;
+ now = get_current_time_us();
+
+ if (now > end)
+ break;
+ } while (status);
+
+ return status ? -EBUSY : 0;
+}
+
+static int nand_spi_set_op_mode(struct nand_base *nand, u8 mode)
+{
+ struct nand_spi *spi_nand = base_to_spi(nand);
+ struct nfi *nfi = nand->nfi;
+ int ret = 0;
+
+ if (spi_nand->op_mode != mode) {
+ ret = nfi->nfi_ctrl(nfi, SNFI_CTRL_OP_MODE, (void *)&mode);
+ spi_nand->op_mode = mode;
+ }
+
+ return ret;
+}
+
+static int nand_spi_set_config(struct nand_base *nand, u8 addr, u8 mask,
+ bool en)
+{
+ u8 configs = 0;
+
+ nand->get_feature(nand, addr, &configs, 1);
+
+ if (en)
+ configs |= mask;
+ else
+ configs &= ~mask;
+
+ nand->set_feature(nand, addr, &configs, 1);
+
+ configs = 0;
+ nand->get_feature(nand, addr, &configs, 1);
+
+ return (configs & mask) == en ? 0 : -EFAULT;
+}
+
+static int nand_spi_die_select(struct nand_base *nand, int *row)
+{
+ struct device_spi *dev = device_to_spi(nand->dev);
+ struct nfi *nfi = nand->nfi;
+ int lun_blocks, block_pages, lun, blocks;
+ int page = *row, ret = 0;
+ u8 param = 0, die_sel;
+
+ if (nand->dev->lun_num < 2)
+ return 0;
+
+ block_pages = nand_block_pages(nand->dev);
+ lun_blocks = nand_lun_blocks(nand->dev);
+ blocks = div_down(page, block_pages);
+ lun = div_down(blocks, lun_blocks);
+
+ if (dev->extend_cmds->die_select == -1) {
+ die_sel = (u8)(lun << dev->feature.character.die_sel_bit);
+ nand->get_feature(nand, dev->feature.character.addr, &param, 1);
+ param |= die_sel;
+ nand->set_feature(nand, dev->feature.character.addr, &param, 1);
+ param = 0;
+ nand->get_feature(nand, dev->feature.character.addr, &param, 1);
+ ret = (param & die_sel) ? 0 : -EFAULT;
+ } else {
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->extend_cmds->die_select);
+ nfi->send_addr(nfi, lun, 0, 1, 0);
+ nfi->trigger(nfi);
+ }
+
+ *row = page - (lun_blocks * block_pages) * lun;
+
+ return ret;
+}
+
+static int nand_spi_select_device(struct nand_base *nand, int cs)
+{
+ struct nand_spi *spi = base_to_spi(nand);
+ struct nand_base *parent = spi->parent;
+
+ nand_spi_set_op_mode(nand, SNFI_MAC_MODE);
+
+ return parent->select_device(nand, cs);
+}
+
+static int nand_spi_reset(struct nand_base *nand)
+{
+ struct nand_spi *spi = base_to_spi(nand);
+ struct nand_base *parent = spi->parent;
+
+ nand_spi_set_op_mode(nand, SNFI_MAC_MODE);
+
+ parent->reset(nand);
+
+ return nand_spi_wait_ready(nand, READY_TIMEOUT);
+}
+
+static int nand_spi_read_id(struct nand_base *nand, u8 *id, int count)
+{
+ struct nand_spi *spi = base_to_spi(nand);
+ struct nand_base *parent = spi->parent;
+
+ nand_spi_set_op_mode(nand, SNFI_MAC_MODE);
+
+ return parent->read_id(nand, id, count);
+}
+
+static int nand_spi_read_param_page(struct nand_base *nand, u8 *data,
+ int count)
+{
+ struct device_spi *dev = device_to_spi(nand->dev);
+ struct nand_spi *spi = base_to_spi(nand);
+ struct nfi *nfi = nand->nfi;
+ int sectors, value;
+ u8 param = 0;
+
+ sectors = div_round_up(count, nfi->sector_size);
+
+ nand->get_feature(nand, dev->feature.config.addr, &param, 1);
+ param |= BIT(dev->feature.config.otp_en_bit);
+ nand->set_feature(nand, dev->feature.config.addr, &param, 1);
+
+ param = 0;
+ nand->get_feature(nand, dev->feature.config.addr, &param, 1);
+ if (param & BIT(dev->feature.config.otp_en_bit)) {
+ value = 0;
+ nfi->nfi_ctrl(nfi, NFI_CTRL_ECC, &value);
+ nand->dev->col_cycle = spi_replace_rx_col_cycle(spi->rx_mode);
+ nand->read_page(nand, 0x01);
+ nand->read_data(nand, 0x01, 0, sectors, data, NULL);
+ }
+
+ param &= ~BIT(dev->feature.config.otp_en_bit);
+ nand->set_feature(nand, dev->feature.config.addr, &param, 1);
+
+ return 0;
+}
+
+static int nand_spi_set_feature(struct nand_base *nand, u8 addr,
+ u8 *param,
+ int count)
+{
+ struct nand_spi *spi = base_to_spi(nand);
+ struct nand_base *parent = spi->parent;
+
+ nand->write_enable(nand);
+
+ nand_spi_set_op_mode(nand, SNFI_MAC_MODE);
+
+ return parent->set_feature(nand, addr, param, count);
+}
+
+static int nand_spi_get_feature(struct nand_base *nand, u8 addr,
+ u8 *param,
+ int count)
+{
+ struct nand_spi *spi = base_to_spi(nand);
+ struct nand_base *parent = spi->parent;
+
+ nand_spi_set_op_mode(nand, SNFI_MAC_MODE);
+
+ return parent->get_feature(nand, addr, param, count);
+}
+
+static int nand_spi_addressing(struct nand_base *nand, int *row,
+ int *col)
+{
+ struct nand_device *dev = nand->dev;
+ int plane, block, block_pages;
+ int ret;
+
+ ret = nand_spi_die_select(nand, row);
+ if (ret)
+ return ret;
+
+ block_pages = nand_block_pages(dev);
+ block = div_down(*row, block_pages);
+
+ plane = block % dev->plane_num;
+ *col |= (plane << dev->addressing->plane_bit_start);
+
+ return 0;
+}
+
+static int nand_spi_read_page(struct nand_base *nand, int row)
+{
+ struct nand_spi *spi = base_to_spi(nand);
+ struct nand_base *parent = spi->parent;
+
+ if (spi->op_mode == SNFI_AUTO_MODE)
+ nand_spi_set_op_mode(nand, SNFI_AUTO_MODE);
+ else
+ nand_spi_set_op_mode(nand, SNFI_MAC_MODE);
+
+ parent->read_page(nand, row);
+
+ return nand_spi_wait_ready(nand, READY_TIMEOUT);
+}
+
+static int nand_spi_read_data(struct nand_base *nand, int row, int col,
+ int sectors, u8 *data, u8 *oob)
+{
+ struct device_spi *dev = device_to_spi(nand->dev);
+ struct nand_spi *spi = base_to_spi(nand);
+ struct nand_base *parent = spi->parent;
+ int ret;
+
+ if ((spi->rx_mode == SNFI_RX_114 || spi->rx_mode == SNFI_RX_144) &&
+ dev->feature.config.need_qe)
+ nand_spi_set_config(nand, dev->feature.config.addr,
+ BIT(0), true);
+
+ nand->dev->col_cycle = spi_replace_rx_col_cycle(spi->rx_mode);
+
+ nand_spi_set_op_mode(nand, SNFI_CUSTOM_MODE);
+
+ ret = parent->read_data(nand, row, col, sectors, data, oob);
+ if (ret)
+ return -ENANDREAD;
+
+ if (spi->ondie_ecc) {
+ ret = nand_spi_read_status(nand);
+ ret &= GENMASK(dev->feature.status.ecc_end_bit,
+ dev->feature.status.ecc_start_bit);
+ ret >>= dev->feature.status.ecc_start_bit;
+ if (ret > nand->dev->endurance->ecc_req)
+ return -ENANDREAD;
+ else if (ret > nand->dev->endurance->max_bitflips)
+ return -ENANDFLIPS;
+ }
+
+ return 0;
+}
+
+static int nand_spi_write_enable(struct nand_base *nand)
+{
+ struct device_spi *dev = device_to_spi(nand->dev);
+ struct nfi *nfi = nand->nfi;
+ int status;
+
+ nand_spi_set_op_mode(nand, SNFI_MAC_MODE);
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->extend_cmds->write_enable);
+
+ nfi->trigger(nfi);
+
+ status = nand_spi_read_status(nand);
+ status &= nand->dev->status->write_protect;
+
+ return !status;
+}
+
+static int nand_spi_program_data(struct nand_base *nand, int row,
+ int col,
+ u8 *data, u8 *oob)
+{
+ struct device_spi *dev = device_to_spi(nand->dev);
+ struct nand_spi *spi = base_to_spi(nand);
+
+ if (spi->tx_mode == SNFI_TX_114 && dev->feature.config.need_qe)
+ nand_spi_set_config(nand, dev->feature.config.addr,
+ BIT(0), true);
+
+ nand_spi_set_op_mode(nand, SNFI_CUSTOM_MODE);
+
+ nand->dev->col_cycle = spi_replace_tx_col_cycle(spi->tx_mode);
+
+ return spi->parent->program_data(nand, row, col, data, oob);
+}
+
+static int nand_spi_program_page(struct nand_base *nand, int row)
+{
+ struct nand_spi *spi = base_to_spi(nand);
+ struct nand_device *dev = nand->dev;
+ struct nfi *nfi = nand->nfi;
+
+ if (spi->op_mode == SNFI_AUTO_MODE)
+ nand_spi_set_op_mode(nand, SNFI_AUTO_MODE);
+ else
+ nand_spi_set_op_mode(nand, SNFI_MAC_MODE);
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->program_2nd);
+ nfi->send_addr(nfi, 0, row, dev->col_cycle, dev->row_cycle);
+ nfi->trigger(nfi);
+
+ return nand_spi_wait_ready(nand, READY_TIMEOUT);
+}
+
+static int nand_spi_erase_block(struct nand_base *nand, int row)
+{
+ struct nand_spi *spi = base_to_spi(nand);
+ struct nand_base *parent = spi->parent;
+
+ nand_spi_set_op_mode(nand, SNFI_MAC_MODE);
+
+ parent->erase_block(nand, row);
+
+ return nand_spi_wait_ready(nand, READY_TIMEOUT);
+}
+
+static int nand_chip_spi_ctrl(struct nand_chip *chip, int cmd,
+ void *args)
+{
+ struct nand_base *nand = chip->nand;
+ struct device_spi *dev = device_to_spi(nand->dev);
+ struct nand_spi *spi = base_to_spi(nand);
+ struct nfi *nfi = nand->nfi;
+ int ret = 0, value = *(int *)args;
+
+ switch (cmd) {
+ case CHIP_CTRL_ONDIE_ECC:
+ spi->ondie_ecc = (bool)value;
+ ret = nand_spi_set_config(nand, dev->feature.config.addr,
+ BIT(dev->feature.config.ecc_en_bit),
+ spi->ondie_ecc);
+ break;
+
+ case SNFI_CTRL_TX_MODE:
+ if (value < 0 || value > SNFI_TX_114)
+ return -EOPNOTSUPP;
+
+ if (dev->tx_mode_mask & BIT(value)) {
+ spi->tx_mode = value;
+ nand->dev->cmds->random_out_1st = spi_replace_tx_cmds(
+ spi->tx_mode);
+ ret = nfi->nfi_ctrl(nfi, cmd, args);
+ }
+
+ break;
+
+ case SNFI_CTRL_RX_MODE:
+ if (value < 0 || value > SNFI_RX_144)
+ return -EOPNOTSUPP;
+
+ if (dev->rx_mode_mask & BIT(value)) {
+ spi->rx_mode = value;
+ nand->dev->cmds->program_1st = spi_replace_rx_cmds(
+ spi->rx_mode);
+ ret = nfi->nfi_ctrl(nfi, cmd, args);
+ }
+
+ break;
+
+ case CHIP_CTRL_OPS_CACHE:
+ case CHIP_CTRL_OPS_MULTI:
+ case CHIP_CTRL_PSLC_MODE:
+ case CHIP_CTRL_DDR_MODE:
+ case CHIP_CTRL_DRIVE_STRENGTH:
+ case CHIP_CTRL_TIMING_MODE:
+ ret = -EOPNOTSUPP;
+ break;
+
+ default:
+ ret = nfi->nfi_ctrl(nfi, cmd, args);
+ break;
+ }
+
+ return ret;
+}
+
+int nand_chip_spi_resume(struct nand_chip *chip)
+{
+ struct nand_base *nand = chip->nand;
+ struct nand_spi *spi = base_to_spi(nand);
+ struct device_spi *dev = device_to_spi(nand->dev);
+ struct nfi *nfi = nand->nfi;
+ struct nfi_format format;
+ u8 mask;
+
+ nand->reset(nand);
+
+ mask = GENMASK(dev->feature.protect.bp_end_bit,
+ dev->feature.protect.bp_start_bit);
+ nand_spi_set_config(nand, dev->feature.config.addr, mask, false);
+ mask = BIT(dev->feature.config.ecc_en_bit);
+ nand_spi_set_config(nand, dev->feature.config.addr, mask,
+ spi->ondie_ecc);
+
+ format.page_size = nand->dev->page_size;
+ format.spare_size = nand->dev->spare_size;
+ format.ecc_req = nand->dev->endurance->ecc_req;
+
+ return nfi->set_format(nfi, &format);
+}
+
+static int nand_spi_set_format(struct nand_base *nand)
+{
+ struct nfi_format format = {
+ nand->dev->page_size,
+ nand->dev->spare_size,
+ nand->dev->endurance->ecc_req
+ };
+
+ return nand->nfi->set_format(nand->nfi, &format);
+}
+
+struct nand_base *nand_device_init(struct nand_chip *chip)
+{
+ struct nand_base *nand;
+ struct nand_spi *spi;
+ struct device_spi *dev;
+ int ret;
+ u8 mask;
+
+ spi = mem_alloc(1, sizeof(struct nand_spi));
+ if (!spi) {
+ pr_info("alloc nand_spi fail\n");
+ return NULL;
+ }
+
+ spi->ondie_ecc = false;
+ spi->op_mode = SNFI_CUSTOM_MODE;
+ spi->rx_mode = SNFI_RX_114;
+ spi->tx_mode = SNFI_TX_114;
+
+ spi->parent = chip->nand;
+ nand = &spi->base;
+ nand->dev = spi->parent->dev;
+ nand->nfi = spi->parent->nfi;
+
+ nand->select_device = nand_spi_select_device;
+ nand->reset = nand_spi_reset;
+ nand->read_id = nand_spi_read_id;
+ nand->read_param_page = nand_spi_read_param_page;
+ nand->set_feature = nand_spi_set_feature;
+ nand->get_feature = nand_spi_get_feature;
+ nand->read_status = nand_spi_read_status;
+ nand->addressing = nand_spi_addressing;
+ nand->read_page = nand_spi_read_page;
+ nand->read_data = nand_spi_read_data;
+ nand->write_enable = nand_spi_write_enable;
+ nand->program_data = nand_spi_program_data;
+ nand->program_page = nand_spi_program_page;
+ nand->erase_block = nand_spi_erase_block;
+
+ chip->chip_ctrl = nand_chip_spi_ctrl;
+ chip->nand_type = NAND_SPI;
+ chip->resume = nand_chip_spi_resume;
+
+ ret = nand_detect_device(nand);
+ if (ret)
+ goto err;
+
+ nand->select_device(nand, 0);
+
+ ret = nand_spi_set_format(nand);
+ if (ret)
+ goto err;
+
+ dev = (struct device_spi *)nand->dev;
+
+ nand->dev->cmds->random_out_1st =
+ spi_replace_rx_cmds(spi->rx_mode);
+ nand->dev->cmds->program_1st =
+ spi_replace_tx_cmds(spi->tx_mode);
+
+ mask = GENMASK(dev->feature.protect.bp_end_bit,
+ dev->feature.protect.bp_start_bit);
+ ret = nand_spi_set_config(nand, dev->feature.protect.addr, mask, false);
+ if (ret)
+ goto err;
+
+ mask = BIT(dev->feature.config.ecc_en_bit);
+ ret = nand_spi_set_config(nand, dev->feature.config.addr, mask,
+ spi->ondie_ecc);
+ if (ret)
+ goto err;
+
+ return nand;
+
+err:
+ mem_free(spi);
+ return NULL;
+}
+
+void nand_exit(struct nand_base *nand)
+{
+ struct nand_spi *spi = base_to_spi(nand);
+
+ nand_base_exit(spi->parent);
+ mem_free(spi);
+}
--- /dev/null
+++ b/drivers/mtd/nandx/core/nand/nand_spi.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NAND_SPI_H__
+#define __NAND_SPI_H__
+
+/*
+ * spi nand handler
+ * @base: spi nand base functions
+ * @parent: common parent nand base functions
+ * @tx_mode: spi bus width of transfer to device
+ * @rx_mode: spi bus width of transfer from device
+ * @op_mode: spi nand controller (NFI) operation mode
+ * @ondie_ecc: spi nand on-die ecc flag
+ */
+
+struct nand_spi {
+ struct nand_base base;
+ struct nand_base *parent;
+ u8 tx_mode;
+ u8 rx_mode;
+ u8 op_mode;
+ bool ondie_ecc;
+};
+
+static inline struct nand_spi *base_to_spi(struct nand_base *base)
+{
+ return container_of(base, struct nand_spi, base);
+}
+
+#endif /* __NAND_SPI_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/core/nand_base.c
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#include "nandx_util.h"
+#include "nandx_core.h"
+#include "nand_chip.h"
+#include "nand_device.h"
+#include "nfi.h"
+#include "nand_base.h"
+
+static int nand_base_select_device(struct nand_base *nand, int cs)
+{
+ struct nfi *nfi = nand->nfi;
+
+ nfi->reset(nfi);
+
+ return nfi->select_chip(nfi, cs);
+}
+
+static int nand_base_reset(struct nand_base *nand)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->reset);
+ nfi->trigger(nfi);
+
+ return nfi->wait_ready(nfi, NAND_WAIT_POLLING, dev->array_timing->tRST);
+}
+
+static int nand_base_read_id(struct nand_base *nand, u8 *id, int count)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->read_id);
+ nfi->wait_ready(nfi, NAND_WAIT_POLLING, dev->array_timing->tWHR);
+ nfi->send_addr(nfi, 0, 0, 1, 0);
+
+ return nfi->read_bytes(nfi, id, count);
+}
+
+static int nand_base_read_param_page(struct nand_base *nand, u8 *data,
+ int count)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->read_param_page);
+ nfi->send_addr(nfi, 0, 0, 1, 0);
+
+ nfi->wait_ready(nfi, NAND_WAIT_POLLING, dev->array_timing->tR);
+
+ return nfi->read_bytes(nfi, data, count);
+}
+
+static int nand_base_set_feature(struct nand_base *nand, u8 addr,
+ u8 *param,
+ int count)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->set_feature);
+ nfi->send_addr(nfi, addr, 0, 1, 0);
+
+ nfi->write_bytes(nfi, param, count);
+
+ return nfi->wait_ready(nfi, NAND_WAIT_POLLING,
+ dev->array_timing->tFEAT);
+}
+
+static int nand_base_get_feature(struct nand_base *nand, u8 addr,
+ u8 *param,
+ int count)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->get_feature);
+ nfi->send_addr(nfi, addr, 0, 1, 0);
+ nfi->wait_ready(nfi, NAND_WAIT_POLLING, dev->array_timing->tFEAT);
+
+ return nfi->read_bytes(nfi, param, count);
+}
+
+static int nand_base_read_status(struct nand_base *nand)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+ u8 status = 0;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->read_status);
+ nfi->wait_ready(nfi, NAND_WAIT_POLLING, dev->array_timing->tWHR);
+ nfi->read_bytes(nfi, &status, 1);
+
+ return status;
+}
+
+static int nand_base_addressing(struct nand_base *nand, int *row,
+ int *col)
+{
+ struct nand_device *dev = nand->dev;
+ int lun, plane, block, page, cs = 0;
+ int block_pages, target_blocks, wl = 0;
+ int icol = *col;
+
+ if (dev->target_num > 1) {
+ block_pages = nand_block_pages(dev);
+ target_blocks = nand_target_blocks(dev);
+ cs = div_down(*row, block_pages * target_blocks);
+ *row -= cs * block_pages * target_blocks;
+ }
+
+ nand->select_device(nand, cs);
+
+ block_pages = nand_block_pages(dev);
+ block = div_down(*row, block_pages);
+ page = *row - block * block_pages;
+ plane = reminder(block, dev->plane_num);
+ lun = div_down(block, nand_lun_blocks(dev));
+
+ wl |= (page << dev->addressing->row_bit_start);
+ wl |= (block << dev->addressing->block_bit_start);
+ wl |= (plane << dev->addressing->plane_bit_start);
+ wl |= (lun << dev->addressing->lun_bit_start);
+
+ *row = wl;
+ *col = icol;
+
+ return 0;
+}
+
+static int nand_base_read_page(struct nand_base *nand, int row)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->read_1st);
+ nfi->send_addr(nfi, 0, row, dev->col_cycle, dev->row_cycle);
+ nfi->send_cmd(nfi, dev->cmds->read_2nd);
+ nfi->trigger(nfi);
+
+ return nfi->wait_ready(nfi, NAND_WAIT_POLLING, dev->array_timing->tR);
+}
+
+static int nand_base_read_data(struct nand_base *nand, int row, int col,
+ int sectors, u8 *data, u8 *oob)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->random_out_1st);
+ nfi->send_addr(nfi, col, row, dev->col_cycle, dev->row_cycle);
+ nfi->send_cmd(nfi, dev->cmds->random_out_2nd);
+ nfi->wait_ready(nfi, NAND_WAIT_POLLING, dev->array_timing->tRCBSY);
+
+ return nfi->read_sectors(nfi, data, oob, sectors);
+}
+
+static int nand_base_write_enable(struct nand_base *nand)
+{
+ struct nand_device *dev = nand->dev;
+ int status;
+
+ status = nand_base_read_status(nand);
+ if (status & dev->status->write_protect)
+ return 0;
+
+ return -ENANDWP;
+}
+
+static int nand_base_program_data(struct nand_base *nand, int row,
+ int col,
+ u8 *data, u8 *oob)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->program_1st);
+ nfi->send_addr(nfi, 0, row, dev->col_cycle, dev->row_cycle);
+
+ return nfi->write_page(nfi, data, oob);
+}
+
+static int nand_base_program_page(struct nand_base *nand, int row)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->program_2nd);
+ nfi->trigger(nfi);
+
+ return nfi->wait_ready(nfi, NAND_WAIT_POLLING,
+ dev->array_timing->tPROG);
+}
+
+static int nand_base_erase_block(struct nand_base *nand, int row)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->erase_1st);
+ nfi->send_addr(nfi, 0, row, 0, dev->row_cycle);
+ nfi->send_cmd(nfi, dev->cmds->erase_2nd);
+ nfi->trigger(nfi);
+
+ return nfi->wait_ready(nfi, NAND_WAIT_POLLING,
+ dev->array_timing->tBERS);
+}
+
+static int nand_base_read_cache(struct nand_base *nand, int row)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->read_1st);
+ nfi->send_addr(nfi, 0, row, dev->col_cycle, dev->row_cycle);
+ nfi->send_cmd(nfi, dev->cmds->read_cache);
+ nfi->trigger(nfi);
+
+ return nfi->wait_ready(nfi, NAND_WAIT_POLLING,
+ dev->array_timing->tRCBSY);
+}
+
+static int nand_base_read_last(struct nand_base *nand)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->read_cache_last);
+ nfi->trigger(nfi);
+
+ return nfi->wait_ready(nfi, NAND_WAIT_POLLING,
+ dev->array_timing->tRCBSY);
+}
+
+static int nand_base_program_cache(struct nand_base *nand)
+{
+ struct nfi *nfi = nand->nfi;
+ struct nand_device *dev = nand->dev;
+
+ nfi->reset(nfi);
+ nfi->send_cmd(nfi, dev->cmds->program_cache);
+ nfi->trigger(nfi);
+
+ return nfi->wait_ready(nfi, NAND_WAIT_POLLING,
+ dev->array_timing->tPCBSY);
+}
+
+struct nand_base *nand_base_init(struct nand_device *dev,
+ struct nfi *nfi)
+{
+ struct nand_base *nand;
+
+ nand = mem_alloc(1, sizeof(struct nand_base));
+ if (!nand)
+ return NULL;
+
+ nand->dev = dev;
+ nand->nfi = nfi;
+ nand->select_device = nand_base_select_device;
+ nand->reset = nand_base_reset;
+ nand->read_id = nand_base_read_id;
+ nand->read_param_page = nand_base_read_param_page;
+ nand->set_feature = nand_base_set_feature;
+ nand->get_feature = nand_base_get_feature;
+ nand->read_status = nand_base_read_status;
+ nand->addressing = nand_base_addressing;
+ nand->read_page = nand_base_read_page;
+ nand->read_data = nand_base_read_data;
+ nand->read_cache = nand_base_read_cache;
+ nand->read_last = nand_base_read_last;
+ nand->write_enable = nand_base_write_enable;
+ nand->program_data = nand_base_program_data;
+ nand->program_page = nand_base_program_page;
+ nand->program_cache = nand_base_program_cache;
+ nand->erase_block = nand_base_erase_block;
+
+ return nand;
+}
+
+void nand_base_exit(struct nand_base *base)
+{
+ nfi_exit(base->nfi);
+ mem_free(base);
+}
--- /dev/null
+++ b/drivers/mtd/nandx/core/nand_base.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NAND_BASE_H__
+#define __NAND_BASE_H__
+
+/*
+ * nand base functions
+ * @dev: nand device infomations
+ * @nfi: nand host controller
+ * @select_device: select one nand device of multi nand on chip
+ * @reset: reset current nand device
+ * @read_id: read current nand id
+ * @read_param_page: read current nand parameters page
+ * @set_feature: configurate the nand device feature
+ * @get_feature: get the nand device feature
+ * @read_status: read nand device status
+ * @addressing: addressing the address to nand device physical address
+ * @read_page: read page data to device cache register
+ * @read_data: read data from device cache register by bus protocol
+ * @read_cache: nand cache read operation for data output
+ * @read_last: nand cache read operation for last page output
+ * @write_enable: enable program/erase for nand, especially spi nand
+ * @program_data: program data to nand device cache register
+ * @program_page: program page data from nand device cache register to array
+ * @program_cache: nand cache program operation for data input
+ * @erase_block: erase nand block operation
+ */
+struct nand_base {
+ struct nand_device *dev;
+ struct nfi *nfi;
+ int (*select_device)(struct nand_base *nand, int cs);
+ int (*reset)(struct nand_base *nand);
+ int (*read_id)(struct nand_base *nand, u8 *id, int count);
+ int (*read_param_page)(struct nand_base *nand, u8 *data, int count);
+ int (*set_feature)(struct nand_base *nand, u8 addr, u8 *param,
+ int count);
+ int (*get_feature)(struct nand_base *nand, u8 addr, u8 *param,
+ int count);
+ int (*read_status)(struct nand_base *nand);
+ int (*addressing)(struct nand_base *nand, int *row, int *col);
+
+ int (*read_page)(struct nand_base *nand, int row);
+ int (*read_data)(struct nand_base *nand, int row, int col, int sectors,
+ u8 *data, u8 *oob);
+ int (*read_cache)(struct nand_base *nand, int row);
+ int (*read_last)(struct nand_base *nand);
+
+ int (*write_enable)(struct nand_base *nand);
+ int (*program_data)(struct nand_base *nand, int row, int col, u8 *data,
+ u8 *oob);
+ int (*program_page)(struct nand_base *nand, int row);
+ int (*program_cache)(struct nand_base *nand);
+
+ int (*erase_block)(struct nand_base *nand, int row);
+};
+
+struct nand_base *nand_base_init(struct nand_device *device,
+ struct nfi *nfi);
+void nand_base_exit(struct nand_base *base);
+
+struct nand_base *nand_device_init(struct nand_chip *nand);
+void nand_exit(struct nand_base *nand);
+
+int nand_detect_device(struct nand_base *nand);
+
+#endif /* __NAND_BASE_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/core/nand_chip.c
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#include "nandx_util.h"
+#include "nandx_core.h"
+#include "nand_chip.h"
+#include "nand_device.h"
+#include "nfi.h"
+#include "nand_base.h"
+
+static int nand_chip_read_page(struct nand_chip *chip,
+ struct nand_ops *ops,
+ int count)
+{
+ struct nand_base *nand = chip->nand;
+ struct nand_device *dev = nand->dev;
+ int i, ret = 0;
+ int row, col, sectors;
+ u8 *data, *oob;
+
+ for (i = 0; i < count; i++) {
+ row = ops[i].row;
+ col = ops[i].col;
+
+ nand->addressing(nand, &row, &col);
+ ops[i].status = nand->read_page(nand, row);
+ if (ops[i].status < 0) {
+ ret = ops[i].status;
+ continue;
+ }
+
+ data = ops[i].data;
+ oob = ops[i].oob;
+ sectors = ops[i].len / chip->sector_size;
+ ops[i].status = nand->read_data(nand, row, col,
+ sectors, data, oob);
+ if (ops[i].status > 0)
+ ops[i].status = ops[i].status >=
+ dev->endurance->max_bitflips ?
+ -ENANDFLIPS : 0;
+
+ ret = min_t(int, ret, ops[i].status);
+ }
+
+ return ret;
+}
+
+static int nand_chip_write_page(struct nand_chip *chip,
+ struct nand_ops *ops,
+ int count)
+{
+ struct nand_base *nand = chip->nand;
+ struct nand_device *dev = nand->dev;
+ int i, ret = 0;
+ int row, col;
+ u8 *data, *oob;
+
+ for (i = 0; i < count; i++) {
+ row = ops[i].row;
+ col = ops[i].col;
+
+ nand->addressing(nand, &row, &col);
+
+ ops[i].status = nand->write_enable(nand);
+ if (ops[i].status) {
+ pr_debug("Write Protect at %x!\n", row);
+ ops[i].status = -ENANDWP;
+ return -ENANDWP;
+ }
+
+ data = ops[i].data;
+ oob = ops[i].oob;
+ ops[i].status = nand->program_data(nand, row, col, data, oob);
+ if (ops[i].status < 0) {
+ ret = ops[i].status;
+ continue;
+ }
+
+ ops[i].status = nand->program_page(nand, row);
+ if (ops[i].status < 0) {
+ ret = ops[i].status;
+ continue;
+ }
+
+ ops[i].status = nand->read_status(nand);
+ if (ops[i].status & dev->status->program_fail)
+ ops[i].status = -ENANDWRITE;
+
+ ret = min_t(int, ret, ops[i].status);
+ }
+
+ return ret;
+}
+
+static int nand_chip_erase_block(struct nand_chip *chip,
+ struct nand_ops *ops,
+ int count)
+{
+ struct nand_base *nand = chip->nand;
+ struct nand_device *dev = nand->dev;
+ int i, ret = 0;
+ int row, col;
+
+ for (i = 0; i < count; i++) {
+ row = ops[i].row;
+ col = ops[i].col;
+
+ nand->addressing(nand, &row, &col);
+
+ ops[i].status = nand->write_enable(nand);
+ if (ops[i].status) {
+ pr_debug("Write Protect at %x!\n", row);
+ ops[i].status = -ENANDWP;
+ return -ENANDWP;
+ }
+
+ ops[i].status = nand->erase_block(nand, row);
+ if (ops[i].status < 0) {
+ ret = ops[i].status;
+ continue;
+ }
+
+ ops[i].status = nand->read_status(nand);
+ if (ops[i].status & dev->status->erase_fail)
+ ops[i].status = -ENANDERASE;
+
+ ret = min_t(int, ret, ops[i].status);
+ }
+
+ return ret;
+}
+
+/* read first bad mark on spare */
+static int nand_chip_is_bad_block(struct nand_chip *chip,
+ struct nand_ops *ops,
+ int count)
+{
+ int i, ret, value;
+ int status = 0;
+ u8 *data, *tmp_buf;
+
+ tmp_buf = mem_alloc(1, chip->page_size);
+ if (!tmp_buf)
+ return -ENOMEM;
+
+ memset(tmp_buf, 0x00, chip->page_size);
+
+ /* Disable ECC */
+ value = 0;
+ ret = chip->chip_ctrl(chip, NFI_CTRL_ECC, &value);
+ if (ret)
+ goto out;
+
+ ret = chip->read_page(chip, ops, count);
+ if (ret)
+ goto out;
+
+ for (i = 0; i < count; i++) {
+ data = ops[i].data;
+
+ /* temp solution for mt7622, because of no bad mark swap */
+ if (!memcmp(data, tmp_buf, chip->page_size)) {
+ ops[i].status = -ENANDBAD;
+ status = -ENANDBAD;
+
+ } else {
+ ops[i].status = 0;
+ }
+ }
+
+ /* Enable ECC */
+ value = 1;
+ ret = chip->chip_ctrl(chip, NFI_CTRL_ECC, &value);
+ if (ret)
+ goto out;
+
+ mem_free(tmp_buf);
+ return status;
+
+out:
+ mem_free(tmp_buf);
+ return ret;
+}
+
+static int nand_chip_ctrl(struct nand_chip *chip, int cmd, void *args)
+{
+ return -EOPNOTSUPP;
+}
+
+static int nand_chip_suspend(struct nand_chip *chip)
+{
+ return 0;
+}
+
+static int nand_chip_resume(struct nand_chip *chip)
+{
+ return 0;
+}
+
+struct nand_chip *nand_chip_init(struct nfi_resource *res)
+{
+ struct nand_chip *chip;
+ struct nand_base *nand;
+ struct nfi *nfi;
+
+ chip = mem_alloc(1, sizeof(struct nand_chip));
+ if (!chip) {
+ pr_info("nand chip alloc fail!\n");
+ return NULL;
+ }
+
+ nfi = nfi_init(res);
+ if (!nfi) {
+ pr_info("nfi init fail!\n");
+ goto nfi_err;
+ }
+
+ nand = nand_base_init(NULL, nfi);
+ if (!nand) {
+ pr_info("nand base init fail!\n");
+ goto base_err;
+ }
+
+ chip->nand = (void *)nand;
+ chip->read_page = nand_chip_read_page;
+ chip->write_page = nand_chip_write_page;
+ chip->erase_block = nand_chip_erase_block;
+ chip->is_bad_block = nand_chip_is_bad_block;
+ chip->chip_ctrl = nand_chip_ctrl;
+ chip->suspend = nand_chip_suspend;
+ chip->resume = nand_chip_resume;
+
+ nand = nand_device_init(chip);
+ if (!nand)
+ goto nand_err;
+
+ chip->nand = (void *)nand;
+ chip->plane_num = nand->dev->plane_num;
+ chip->block_num = nand_total_blocks(nand->dev);
+ chip->block_size = nand->dev->block_size;
+ chip->block_pages = nand_block_pages(nand->dev);
+ chip->page_size = nand->dev->page_size;
+ chip->oob_size = nfi->fdm_size * div_down(chip->page_size,
+ nfi->sector_size);
+ chip->sector_size = nfi->sector_size;
+ chip->sector_spare_size = nfi->sector_spare_size;
+ chip->min_program_pages = nand->dev->min_program_pages;
+ chip->ecc_strength = nfi->ecc_strength;
+ chip->ecc_parity_size = nfi->ecc_parity_size;
+ chip->fdm_ecc_size = nfi->fdm_ecc_size;
+ chip->fdm_reg_size = nfi->fdm_size;
+
+ return chip;
+
+nand_err:
+ mem_free(nand);
+base_err:
+ nfi_exit(nfi);
+nfi_err:
+ mem_free(chip);
+ return NULL;
+}
+
+void nand_chip_exit(struct nand_chip *chip)
+{
+ nand_exit(chip->nand);
+ mem_free(chip);
+}
--- /dev/null
+++ b/drivers/mtd/nandx/core/nand_chip.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NAND_CHIP_H__
+#define __NAND_CHIP_H__
+
+enum nand_type {
+ NAND_SPI,
+ NAND_SLC,
+ NAND_MLC,
+ NAND_TLC
+};
+
+/*
+ * nand chip operation unit
+ * one nand_ops indicates one row operation
+ * @row: nand chip row address, like as nand row
+ * @col: nand chip column address, like as nand column
+ * @len: operate data length, min is sector_size,
+ * max is page_size and sector_size aligned
+ * @status: one operation result status
+ * @data: data buffer for operation
+ * @oob: oob buffer for operation, like as nand spare area
+ */
+struct nand_ops {
+ int row;
+ int col;
+ int len;
+ int status;
+ void *data;
+ void *oob;
+};
+
+/*
+ * nand chip descriptions
+ * nand chip includes nand controller and the several same nand devices
+ * @nand_type: the nand type on this chip,
+ * the chip maybe have several nand device and the type must be same
+ * @plane_num: the whole plane number on the chip
+ * @block_num: the whole block number on the chip
+ * @block_size: nand device block size
+ * @block_pages: nand device block has page number
+ * @page_size: nand device page size
+ * @oob_size: chip out of band size, like as nand spare szie,
+ * but restricts this:
+ * the size is provied by nand controller(NFI),
+ * because NFI would use some nand spare size
+ * @min_program_pages: chip needs min pages per program operations
+ * one page as one nand_ops
+ * @sector_size: chip min read size
+ * @sector_spare_size: spare size for sector, is spare_size/page_sectors
+ * @ecc_strength: ecc stregth per sector_size, it would be for calculated ecc
+ * @ecc_parity_size: ecc parity size for one sector_size data
+ * @nand: pointer to inherited struct nand_base
+ * @read_page: read %count pages on chip
+ * @write_page: write %count pages on chip
+ * @erase_block: erase %count blocks on chip, one block is one nand_ops
+ * it is better to set nand_ops.row to block start row
+ * @is_bad_block: judge the %count blocks on chip if they are bad
+ * by vendor specification
+ * @chip_ctrl: control the chip features by nandx_ctrl_cmd
+ * @suspend: suspend nand chip
+ * @resume: resume nand chip
+ */
+struct nand_chip {
+ int nand_type;
+ int plane_num;
+ int block_num;
+ int block_size;
+ int block_pages;
+ int page_size;
+ int oob_size;
+
+ int min_program_pages;
+ int sector_size;
+ int sector_spare_size;
+ int ecc_strength;
+ int ecc_parity_size;
+ u32 fdm_ecc_size;
+ u32 fdm_reg_size;
+
+ void *nand;
+
+ int (*read_page)(struct nand_chip *chip, struct nand_ops *ops,
+ int count);
+ int (*write_page)(struct nand_chip *chip, struct nand_ops *ops,
+ int count);
+ int (*erase_block)(struct nand_chip *chip, struct nand_ops *ops,
+ int count);
+ int (*is_bad_block)(struct nand_chip *chip, struct nand_ops *ops,
+ int count);
+ int (*chip_ctrl)(struct nand_chip *chip, int cmd, void *args);
+ int (*suspend)(struct nand_chip *chip);
+ int (*resume)(struct nand_chip *chip);
+};
+
+struct nand_chip *nand_chip_init(struct nfi_resource *res);
+void nand_chip_exit(struct nand_chip *chip);
+#endif /* __NAND_CHIP_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/core/nand_device.c
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#include "nandx_util.h"
+#include "nandx_core.h"
+#include "nand_chip.h"
+#include "nand_device.h"
+#include "nand_base.h"
+
+#define MAX_CHIP_DEVICE 4
+#define PARAM_PAGE_LEN 2048
+#define ONFI_CRC_BASE 0x4f4e
+
+static u16 nand_onfi_crc16(u16 crc, u8 const *p, size_t len)
+{
+ int i;
+
+ while (len--) {
+ crc ^= *p++ << 8;
+
+ for (i = 0; i < 8; i++)
+ crc = (crc << 1) ^ ((crc & 0x8000) ? 0x8005 : 0);
+ }
+
+ return crc;
+}
+
+static inline void decode_addr_cycle(u8 addr_cycle, u8 *row_cycle,
+ u8 *col_cycle)
+{
+ *row_cycle = addr_cycle & 0xf;
+ *col_cycle = (addr_cycle >> 4) & 0xf;
+}
+
+static int detect_onfi(struct nand_device *dev,
+ struct nand_onfi_params *onfi)
+{
+ struct nand_endurance *endurance = dev->endurance;
+ u16 size, i, crc16;
+ u8 *id;
+
+ size = sizeof(struct nand_onfi_params) - sizeof(u16);
+
+ for (i = 0; i < 3; i++) {
+ crc16 = nand_onfi_crc16(ONFI_CRC_BASE, (u8 *)&onfi[i], size);
+
+ if (onfi[i].signature[0] == 'O' &&
+ onfi[i].signature[1] == 'N' &&
+ onfi[i].signature[2] == 'F' &&
+ onfi[i].signature[3] == 'I' &&
+ onfi[i].crc16 == crc16)
+ break;
+
+ /* in some spi nand, onfi signature maybe "NAND" */
+ if (onfi[i].signature[0] == 'N' &&
+ onfi[i].signature[1] == 'A' &&
+ onfi[i].signature[2] == 'N' &&
+ onfi[i].signature[3] == 'D' &&
+ onfi[i].crc16 == crc16)
+ break;
+ }
+
+ if (i == 3)
+ return -ENODEV;
+
+ memcpy(dev->name, onfi[i].model, 20);
+ id = onfi[i].manufacturer;
+ dev->id = NAND_PACK_ID(id[0], id[1], id[2], id[3], id[4], id[5], id[6],
+ id[7]);
+ dev->id_len = MAX_ID_NUM;
+ dev->io_width = (onfi[i].features & 1) ? NAND_IO16 : NAND_IO8;
+ decode_addr_cycle(onfi[i].addr_cycle, &dev->row_cycle,
+ &dev->col_cycle);
+ dev->target_num = 1;
+ dev->lun_num = onfi[i].lun_num;
+ dev->plane_num = BIT(onfi[i].plane_address_bits);
+ dev->block_num = onfi[i].lun_blocks / dev->plane_num;
+ dev->block_size = onfi[i].block_pages * onfi[i].page_size;
+ dev->page_size = onfi[i].page_size;
+ dev->spare_size = onfi[i].spare_size;
+
+ endurance->ecc_req = onfi[i].ecc_req;
+ endurance->pe_cycle = onfi[i].valid_block_endurance;
+ endurance->max_bitflips = endurance->ecc_req >> 1;
+
+ return 0;
+}
+
+static int detect_jedec(struct nand_device *dev,
+ struct nand_jedec_params *jedec)
+{
+ struct nand_endurance *endurance = dev->endurance;
+ u16 size, i, crc16;
+ u8 *id;
+
+ size = sizeof(struct nand_jedec_params) - sizeof(u16);
+
+ for (i = 0; i < 3; i++) {
+ crc16 = nand_onfi_crc16(ONFI_CRC_BASE, (u8 *)&jedec[i], size);
+
+ if (jedec[i].signature[0] == 'J' &&
+ jedec[i].signature[1] == 'E' &&
+ jedec[i].signature[2] == 'S' &&
+ jedec[i].signature[3] == 'D' &&
+ jedec[i].crc16 == crc16)
+ break;
+ }
+
+ if (i == 3)
+ return -ENODEV;
+
+ memcpy(dev->name, jedec[i].model, 20);
+ id = jedec[i].manufacturer;
+ dev->id = NAND_PACK_ID(id[0], id[1], id[2], id[3], id[4], id[5], id[6],
+ id[7]);
+ dev->id_len = MAX_ID_NUM;
+ dev->io_width = (jedec[i].features & 1) ? NAND_IO16 : NAND_IO8;
+ decode_addr_cycle(jedec[i].addr_cycle, &dev->row_cycle,
+ &dev->col_cycle);
+ dev->target_num = 1;
+ dev->lun_num = jedec[i].lun_num;
+ dev->plane_num = BIT(jedec[i].plane_address_bits);
+ dev->block_num = jedec[i].lun_blocks / dev->plane_num;
+ dev->block_size = jedec[i].block_pages * jedec[i].page_size;
+ dev->page_size = jedec[i].page_size;
+ dev->spare_size = jedec[i].spare_size;
+
+ endurance->ecc_req = jedec[i].endurance_block0[0];
+ endurance->pe_cycle = jedec[i].valid_block_endurance;
+ endurance->max_bitflips = endurance->ecc_req >> 1;
+
+ return 0;
+}
+
+static struct nand_device *detect_parameters_page(struct nand_base
+ *nand)
+{
+ struct nand_device *dev = nand->dev;
+ void *params;
+ int ret;
+
+ params = mem_alloc(1, PARAM_PAGE_LEN);
+ if (!params)
+ return NULL;
+
+ memset(params, 0, PARAM_PAGE_LEN);
+ ret = nand->read_param_page(nand, params, PARAM_PAGE_LEN);
+ if (ret < 0) {
+ pr_info("read parameters page fail!\n");
+ goto error;
+ }
+
+ ret = detect_onfi(dev, params);
+ if (ret) {
+ pr_info("detect onfi device fail! try to detect jedec\n");
+ ret = detect_jedec(dev, params);
+ if (ret) {
+ pr_info("detect jedec device fail!\n");
+ goto error;
+ }
+ }
+
+ mem_free(params);
+ return dev;
+
+error:
+ mem_free(params);
+ return NULL;
+}
+
+static int read_device_id(struct nand_base *nand, int cs, u8 *id)
+{
+ int i;
+
+ nand->select_device(nand, cs);
+ nand->reset(nand);
+ nand->read_id(nand, id, MAX_ID_NUM);
+ pr_info("device %d ID: ", cs);
+
+ for (i = 0; i < MAX_ID_NUM; i++)
+ pr_info("%x ", id[i]);
+
+ pr_info("\n");
+
+ return 0;
+}
+
+static int detect_more_device(struct nand_base *nand, u8 *id)
+{
+ u8 id_ext[MAX_ID_NUM];
+ int i, j, target_num = 0;
+
+ for (i = 1; i < MAX_CHIP_DEVICE; i++) {
+ memset(id_ext, 0xff, MAX_ID_NUM);
+ read_device_id(nand, i, id_ext);
+
+ for (j = 0; j < MAX_ID_NUM; j++) {
+ if (id_ext[j] != id[j])
+ goto out;
+ }
+
+ target_num += 1;
+ }
+
+out:
+ return target_num;
+}
+
+static struct nand_device *scan_device_table(const u8 *id, int id_len)
+{
+ struct nand_device *dev;
+ int i = 0, j;
+ u8 ids[MAX_ID_NUM] = {0};
+
+ while (1) {
+ dev = nand_get_device(i);
+
+ if (!strcmp(dev->name, "NO-DEVICE"))
+ break;
+
+ if (id_len < dev->id_len) {
+ i += 1;
+ continue;
+ }
+
+ NAND_UNPACK_ID(dev->id, ids, MAX_ID_NUM);
+ for (j = 0; j < dev->id_len; j++) {
+ if (ids[j] != id[j])
+ break;
+ }
+
+ if (j == dev->id_len)
+ break;
+
+ i += 1;
+ }
+
+ return dev;
+}
+
+int nand_detect_device(struct nand_base *nand)
+{
+ struct nand_device *dev;
+ u8 id[MAX_ID_NUM] = { 0 };
+ int target_num = 0;
+
+ /* Get nand device default setting for reset/read_id */
+ nand->dev = scan_device_table(NULL, -1);
+
+ read_device_id(nand, 0, id);
+ dev = scan_device_table(id, MAX_ID_NUM);
+
+ if (!strcmp(dev->name, "NO-DEVICE")) {
+ pr_info("device scan fail\n");
+ return -ENODEV;
+ }
+
+ /* TobeFix: has null pointer issue in this funciton */
+ if (!strcmp(dev->name, "NO-DEVICE")) {
+ pr_info("device scan fail, detect parameters page\n");
+ dev = detect_parameters_page(nand);
+ if (!dev) {
+ pr_info("detect parameters fail\n");
+ return -ENODEV;
+ }
+ }
+
+ if (dev->target_num > 1)
+ target_num = detect_more_device(nand, id);
+
+ target_num += 1;
+ pr_debug("chip has target device num: %d\n", target_num);
+
+ if (dev->target_num != target_num)
+ dev->target_num = target_num;
+
+ nand->dev = dev;
+
+ return 0;
+}
+
--- /dev/null
+++ b/drivers/mtd/nandx/core/nand_device.h
@@ -0,0 +1,608 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NAND_DEVICE_H__
+#define __NAND_DEVICE_H__
+
+/* onfi 3.2 */
+struct nand_onfi_params {
+ /* Revision information and features block. 0 */
+ /*
+ * Byte 0: 4Fh,
+ * Byte 1: 4Eh,
+ * Byte 2: 46h,
+ * Byte 3: 49h,
+ */
+ u8 signature[4];
+ /*
+ * 9-15 Reserved (0)
+ * 8 1 = supports ONFI version 3.2
+ * 7 1 = supports ONFI version 3.1
+ * 6 1 = supports ONFI version 3.0
+ * 5 1 = supports ONFI version 2.3
+ * 4 1 = supports ONFI version 2.2
+ * 3 1 = supports ONFI version 2.1
+ * 2 1 = supports ONFI version 2.0
+ * 1 1 = supports ONFI version 1.0
+ * 0 Reserved (0)
+ */
+ u16 revision;
+ /*
+ * 13-15 Reserved (0)
+ * 12 1 = supports external Vpp
+ * 11 1 = supports Volume addressing
+ * 10 1 = supports NV-DDR2
+ * 9 1 = supports EZ NAND
+ * 8 1 = supports program page register clear enhancement
+ * 7 1 = supports extended parameter page
+ * 6 1 = supports multi-plane read operations
+ * 5 1 = supports NV-DDR
+ * 4 1 = supports odd to even page Copyback
+ * 3 1 = supports multi-plane program and erase operations
+ * 2 1 = supports non-sequential page programming
+ * 1 1 = supports multiple LUN operations
+ * 0 1 = supports 16-bit data bus width
+ */
+ u16 features;
+ /*
+ * 13-15 Reserved (0)
+ * 12 1 = supports LUN Get and LUN Set Features
+ * 11 1 = supports ODT Configure
+ * 10 1 = supports Volume Select
+ * 9 1 = supports Reset LUN
+ * 8 1 = supports Small Data Move
+ * 7 1 = supports Change Row Address
+ * 6 1 = supports Change Read Column Enhanced
+ * 5 1 = supports Read Unique ID
+ * 4 1 = supports Copyback
+ * 3 1 = supports Read Status Enhanced
+ * 2 1 = supports Get Features and Set Features
+ * 1 1 = supports Read Cache commands
+ * 0 1 = supports Page Cache Program command
+ */
+ u16 opt_cmds;
+ /*
+ * 4-7 Reserved (0)
+ * 3 1 = supports Multi-plane Block Erase
+ * 2 1 = supports Multi-plane Copyback Program
+ * 1 1 = supports Multi-plane Page Program
+ * 0 1 = supports Random Data Out
+ */
+ u8 advance_cmds;
+ u8 reserved0[1];
+ u16 extend_param_len;
+ u8 param_page_num;
+ u8 reserved1[17];
+
+ /* Manufacturer information block. 32 */
+ u8 manufacturer[12];
+ u8 model[20];
+ u8 jedec_id;
+ u16 data_code;
+ u8 reserved2[13];
+
+ /* Memory organization block. 80 */
+ u32 page_size;
+ u16 spare_size;
+ u32 partial_page_size; /* obsolete */
+ u16 partial_spare_size; /* obsolete */
+ u32 block_pages;
+ u32 lun_blocks;
+ u8 lun_num;
+ /*
+ * 4-7 Column address cycles
+ * 0-3 Row address cycles
+ */
+ u8 addr_cycle;
+ u8 cell_bits;
+ u16 lun_max_bad_blocks;
+ u16 block_endurance;
+ u8 target_begin_valid_blocks;
+ u16 valid_block_endurance;
+ u8 page_program_num;
+ u8 partial_program_attr; /* obsolete */
+ u8 ecc_req;
+ /*
+ * 4-7 Reserved (0)
+ * 0-3 Number of plane address bits
+ */
+ u8 plane_address_bits;
+ /*
+ * 6-7 Reserved (0)
+ * 5 1 = lower bit XNOR block address restriction
+ * 4 1 = read cache supported
+ * 3 Address restrictions for cache operations
+ * 2 1 = program cache supported
+ * 1 1 = no block address restrictions
+ * 0 Overlapped / concurrent multi-plane support
+ */
+ u8 multi_plane_attr;
+ u8 ez_nand_support;
+ u8 reserved3[12];
+
+ /* Electrical parameters block. 128 */
+ u8 io_pin_max_capacitance;
+ /*
+ * 6-15 Reserved (0)
+ * 5 1 = supports timing mode 5
+ * 4 1 = supports timing mode 4
+ * 3 1 = supports timing mode 3
+ * 2 1 = supports timing mode 2
+ * 1 1 = supports timing mode 1
+ * 0 1 = supports timing mode 0, shall be 1
+ */
+ u16 sdr_timing_mode;
+ u16 sdr_program_cache_timing_mode; /* obsolete */
+ u16 tPROG;
+ u16 tBERS;
+ u16 tR;
+ u16 tCCS;
+ /*
+ * 7 Reserved (0)
+ * 6 1 = supports NV-DDR2 timing mode 8
+ * 5 1 = supports NV-DDR timing mode 5
+ * 4 1 = supports NV-DDR timing mode 4
+ * 3 1 = supports NV-DDR timing mode 3
+ * 2 1 = supports NV-DDR timing mode 2
+ * 1 1 = supports NV-DDR timing mode 1
+ * 0 1 = supports NV-DDR timing mode 0
+ */
+ u8 nvddr_timing_mode;
+ /*
+ * 7 1 = supports timing mode 7
+ * 6 1 = supports timing mode 6
+ * 5 1 = supports timing mode 5
+ * 4 1 = supports timing mode 4
+ * 3 1 = supports timing mode 3
+ * 2 1 = supports timing mode 2
+ * 1 1 = supports timing mode 1
+ * 0 1 = supports timing mode 0
+ */
+ u8 nvddr2_timing_mode;
+ /*
+ * 4-7 Reserved (0)
+ * 3 1 = device requires Vpp enablement sequence
+ * 2 1 = device supports CLK stopped for data input
+ * 1 1 = typical capacitance
+ * 0 tCAD value to use
+ */
+ u8 nvddr_fetures;
+ u16 clk_pin_capacitance;
+ u16 io_pin_capacitance;
+ u16 input_pin_capacitance;
+ u8 input_pin_max_capacitance;
+ /*
+ * 3-7 Reserved (0)
+ * 2 1 = supports 18 Ohm drive strength
+ * 1 1 = supports 25 Ohm drive strength
+ * 0 1 = supports driver strength settings
+ */
+ u8 drive_strength;
+ u16 tR_multi_plane;
+ u16 tADL;
+ u16 tR_ez_nand;
+ /*
+ * 6-7 Reserved (0)
+ * 5 1 = external VREFQ required for >= 200 MT/s
+ * 4 1 = supports differential signaling for DQS
+ * 3 1 = supports differential signaling for RE_n
+ * 2 1 = supports ODT value of 30 Ohms
+ * 1 1 = supports matrix termination ODT
+ * 0 1 = supports self-termination ODT
+ */
+ u8 nvddr2_features;
+ u8 nvddr2_warmup_cycles;
+ u8 reserved4[4];
+
+ /* vendor block. 164 */
+ u16 vendor_revision;
+ u8 vendor_spec[88];
+
+ /* CRC for Parameter Page. 254 */
+ u16 crc16;
+} __packed;
+
+/* JESD230-B */
+struct nand_jedec_params {
+ /* Revision information and features block. 0 */
+ /*
+ * Byte 0:4Ah
+ * Byte 1:45h
+ * Byte 2:53h
+ * Byte 3:44h
+ */
+ u8 signature[4];
+ /*
+ * 3-15: Reserved (0)
+ * 2: 1 = supports parameter page revision 1.0 and standard revision 1.0
+ * 1: 1 = supports vendor specific parameter page
+ * 0: Reserved (0)
+ */
+ u16 revision;
+ /*
+ * 9-15 Reserved (0)
+ * 8: 1 = supports program page register clear enhancement
+ * 7: 1 = supports external Vpp
+ * 6: 1 = supports Toggle Mode DDR
+ * 5: 1 = supports Synchronous DDR
+ * 4: 1 = supports multi-plane read operations
+ * 3: 1 = supports multi-plane program and erase operations
+ * 2: 1 = supports non-sequential page programming
+ * 1: 1 = supports multiple LUN operations
+ * 0: 1 = supports 16-bit data bus width
+ */
+ u16 features;
+ /*
+ * 11-23: Reserved (0)
+ * 10: 1 = supports Synchronous Reset
+ * 9: 1 = supports Reset LUN (Primary)
+ * 8: 1 = supports Small Data Move
+ * 7: 1 = supports Multi-plane Copyback Program (Primary)
+ * 6: 1 = supports Random Data Out (Primary)
+ * 5: 1 = supports Read Unique ID
+ * 4: 1 = supports Copyback
+ * 3: 1 = supports Read Status Enhanced (Primary)
+ * 2: 1 = supports Get Features and Set Features
+ * 1: 1 = supports Read Cache commands
+ * 0: 1 = supports Page Cache Program command
+ */
+ u8 opt_cmds[3];
+ /*
+ * 8-15: Reserved (0)
+ * 7: 1 = supports secondary Read Status Enhanced
+ * 6: 1 = supports secondary Multi-plane Block Erase
+ * 5: 1 = supports secondary Multi-plane Copyback Program
+ * 4: 1 = supports secondary Multi-plane Program
+ * 3: 1 = supports secondary Random Data Out
+ * 2: 1 = supports secondary Multi-plane Copyback Read
+ * 1: 1 = supports secondary Multi-plane Read Cache Random
+ * 0: 1 = supports secondary Multi-plane Read
+ */
+ u16 secondary_cmds;
+ u8 param_page_num;
+ u8 reserved0[18];
+
+ /* Manufacturer information block. 32*/
+ u8 manufacturer[12];
+ u8 model[20];
+ u8 jedec_id[6];
+ u8 reserved1[10];
+
+ /* Memory organization block. 80 */
+ u32 page_size;
+ u16 spare_size;
+ u8 reserved2[6];
+ u32 block_pages;
+ u32 lun_blocks;
+ u8 lun_num;
+ /*
+ * 4-7 Column address cycles
+ * 0-3 Row address cycles
+ */
+ u8 addr_cycle;
+ u8 cell_bits;
+ u8 page_program_num;
+ /*
+ * 4-7 Reserved (0)
+ * 0-3 Number of plane address bits
+ */
+ u8 plane_address_bits;
+ /*
+ * 3-7: Reserved (0)
+ * 2: 1= read cache supported
+ * 1: 1 = program cache supported
+ * 0: 1= No multi-plane block address restrictions
+ */
+ u8 multi_plane_attr;
+ u8 reserved3[38];
+
+ /* Electrical parameters block. 144 */
+ /*
+ * 6-15: Reserved (0)
+ * 5: 1 = supports 20 ns speed grade (50 MHz)
+ * 4: 1 = supports 25 ns speed grade (40 MHz)
+ * 3: 1 = supports 30 ns speed grade (~33 MHz)
+ * 2: 1 = supports 35 ns speed grade (~28 MHz)
+ * 1: 1 = supports 50 ns speed grade (20 MHz)
+ * 0: 1 = supports 100 ns speed grade (10 MHz)
+ */
+ u16 sdr_speed;
+ /*
+ * 8-15: Reserved (0)
+ * 7: 1 = supports 5 ns speed grade (200 MHz)
+ * 6: 1 = supports 6 ns speed grade (~166 MHz)
+ * 5: 1 = supports 7.5 ns speed grade (~133 MHz)
+ * 4: 1 = supports 10 ns speed grade (100 MHz)
+ * 3: 1 = supports 12 ns speed grade (~83 MHz)
+ * 2: 1 = supports 15 ns speed grade (~66 MHz)
+ * 1: 1 = supports 25 ns speed grade (40 MHz)
+ * 0: 1 = supports 30 ns speed grade (~33 MHz)
+ */
+ u16 toggle_ddr_speed;
+ /*
+ * 6-15: Reserved (0)
+ * 5: 1 = supports 10 ns speed grade (100 MHz)
+ * 4: 1 = supports 12 ns speed grade (~83 MHz)
+ * 3: 1 = supports 15 ns speed grade (~66 MHz)
+ * 2: 1 = supports 20 ns speed grade (50 MHz)
+ * 1: 1 = supports 30 ns speed grade (~33 MHz)
+ * 0: 1 = supports 50 ns speed grade (20 MHz)
+ */
+ u16 sync_ddr_speed;
+ u8 sdr_features;
+ u8 toggle_ddr_features;
+ /*
+ * 2-7: Reserved (0)
+ * 1: Device supports CK stopped for data input
+ * 0: tCAD value to use
+ */
+ u8 sync_ddr_features;
+ u16 tPROG;
+ u16 tBERS;
+ u16 tR;
+ u16 tR_multi_plane;
+ u16 tCCS;
+ u16 io_pin_capacitance;
+ u16 input_pin_capacitance;
+ u16 ck_pin_capacitance;
+ /*
+ * 3-7: Reserved (0)
+ * 2: 1 = supports 18 ohm drive strength
+ * 1: 1 = supports 25 ohm drive strength
+ * 0: 1 = supports 35ohm/50ohm drive strength
+ */
+ u8 drive_strength;
+ u16 tADL;
+ u8 reserved4[36];
+
+ /* ECC and endurance block. 208 */
+ u8 target_begin_valid_blocks;
+ u16 valid_block_endurance;
+ /*
+ * Byte 0: Number of bits ECC correctability
+ * Byte 1: Codeword size
+ * Byte 2-3: Bad blocks maximum per LUN
+ * Byte 4-5: Block endurance
+ * Byte 6-7: Reserved (0)
+ */
+ u8 endurance_block0[8];
+ u8 endurance_block1[8];
+ u8 endurance_block2[8];
+ u8 endurance_block3[8];
+ u8 reserved5[29];
+
+ /* Reserved. 272 */
+ u8 reserved6[148];
+
+ /* Vendor specific block. 420 */
+ u16 vendor_revision;
+ u8 vendor_spec[88];
+
+ /* CRC for Parameter Page. 510 */
+ u16 crc16;
+} __packed;
+
+/* parallel nand io width */
+enum nand_io_width {
+ NAND_IO8,
+ NAND_IO16
+};
+
+/* all supported nand timming type */
+enum nand_timing_type {
+ NAND_TIMING_SDR,
+ NAND_TIMING_SYNC_DDR,
+ NAND_TIMING_TOGGLE_DDR,
+ NAND_TIMING_NVDDR2
+};
+
+/* nand basic commands */
+struct nand_cmds {
+ short reset;
+ short read_id;
+ short read_status;
+ short read_param_page;
+ short set_feature;
+ short get_feature;
+ short read_1st;
+ short read_2nd;
+ short random_out_1st;
+ short random_out_2nd;
+ short program_1st;
+ short program_2nd;
+ short erase_1st;
+ short erase_2nd;
+ short read_cache;
+ short read_cache_last;
+ short program_cache;
+};
+
+/*
+ * addressing for nand physical address
+ * @row_bit_start: row address start bit
+ * @block_bit_start: block address start bit
+ * @plane_bit_start: plane address start bit
+ * @lun_bit_start: lun address start bit
+ */
+struct nand_addressing {
+ u8 row_bit_start;
+ u8 block_bit_start;
+ u8 plane_bit_start;
+ u8 lun_bit_start;
+};
+
+/*
+ * nand operations status
+ * @array_busy: indicates device array operation busy
+ * @write_protect: indicates the device cannot be wrote or erased
+ * @erase_fail: indicates erase operation fail
+ * @program_fail: indicates program operation fail
+ */
+struct nand_status {
+ u8 array_busy;
+ u8 write_protect;
+ u8 erase_fail;
+ u8 program_fail;
+};
+
+/*
+ * nand endurance information
+ * @pe_cycle: max program/erase cycle for nand stored data stability
+ * @ecc_req: ecc strength required for the nand, measured per 1KB
+ * @max_bitflips: bitflips is ecc corrected bits,
+ * max_bitflips is the threshold for nand stored data stability
+ * if corrected bits is over max_bitflips, stored data must be moved
+ * to another good block
+ */
+struct nand_endurance {
+ int pe_cycle;
+ int ecc_req;
+ int max_bitflips;
+};
+
+/* wait for nand busy type */
+enum nand_wait_type {
+ NAND_WAIT_IRQ,
+ NAND_WAIT_POLLING,
+ NAND_WAIT_TWHR2,
+};
+
+/* each nand array operations time */
+struct nand_array_timing {
+ u16 tRST;
+ u16 tWHR;
+ u16 tR;
+ u16 tRCBSY;
+ u16 tFEAT;
+ u16 tPROG;
+ u16 tPCBSY;
+ u16 tBERS;
+ u16 tDBSY;
+};
+
+/* nand sdr interface timing required */
+struct nand_sdr_timing {
+ u16 tREA;
+ u16 tREH;
+ u16 tCR;
+ u16 tRP;
+ u16 tWP;
+ u16 tWH;
+ u16 tWHR;
+ u16 tCLS;
+ u16 tALS;
+ u16 tCLH;
+ u16 tALH;
+ u16 tWC;
+ u16 tRC;
+};
+
+/* nand onfi ddr (nvddr) interface timing required */
+struct nand_onfi_timing {
+ u16 tCAD;
+ u16 tWPRE;
+ u16 tWPST;
+ u16 tWRCK;
+ u16 tDQSCK;
+ u16 tWHR;
+};
+
+/* nand toggle ddr (toggle 1.0) interface timing required */
+struct nand_toggle_timing {
+ u16 tCS;
+ u16 tCH;
+ u16 tCAS;
+ u16 tCAH;
+ u16 tCALS;
+ u16 tCALH;
+ u16 tWP;
+ u16 tWPRE;
+ u16 tWPST;
+ u16 tWPSTH;
+ u16 tCR;
+ u16 tRPRE;
+ u16 tRPST;
+ u16 tRPSTH;
+ u16 tCDQSS;
+ u16 tWHR;
+};
+
+/* nand basic device information */
+struct nand_device {
+ u8 *name;
+ u64 id;
+ u8 id_len;
+ u8 io_width;
+ u8 row_cycle;
+ u8 col_cycle;
+ u8 target_num;
+ u8 lun_num;
+ u8 plane_num;
+ int block_num;
+ int block_size;
+ int page_size;
+ int spare_size;
+ int min_program_pages;
+ struct nand_cmds *cmds;
+ struct nand_addressing *addressing;
+ struct nand_status *status;
+ struct nand_endurance *endurance;
+ struct nand_array_timing *array_timing;
+};
+
+#define NAND_DEVICE(_name, _id, _id_len, _io_width, _row_cycle, \
+ _col_cycle, _target_num, _lun_num, _plane_num, \
+ _block_num, _block_size, _page_size, _spare_size, \
+ _min_program_pages, _cmds, _addressing, _status, \
+ _endurance, _array_timing) \
+{ \
+ _name, _id, _id_len, _io_width, _row_cycle, \
+ _col_cycle, _target_num, _lun_num, _plane_num, \
+ _block_num, _block_size, _page_size, _spare_size, \
+ _min_program_pages, _cmds, _addressing, _status, \
+ _endurance, _array_timing \
+}
+
+#define MAX_ID_NUM sizeof(u64)
+
+#define NAND_PACK_ID(id0, id1, id2, id3, id4, id5, id6, id7) \
+ ( \
+ id0 | id1 << 8 | id2 << 16 | id3 << 24 | \
+ (u64)id4 << 32 | (u64)id5 << 40 | \
+ (u64)id6 << 48 | (u64)id7 << 56 \
+ )
+
+#define NAND_UNPACK_ID(id, ids, len) \
+ do { \
+ int _i; \
+ for (_i = 0; _i < len; _i++) \
+ ids[_i] = id >> (_i << 3) & 0xff; \
+ } while (0)
+
+static inline int nand_block_pages(struct nand_device *device)
+{
+ return div_down(device->block_size, device->page_size);
+}
+
+static inline int nand_lun_blocks(struct nand_device *device)
+{
+ return device->plane_num * device->block_num;
+}
+
+static inline int nand_target_blocks(struct nand_device *device)
+{
+ return device->lun_num * device->plane_num * device->block_num;
+}
+
+static inline int nand_total_blocks(struct nand_device *device)
+{
+ return device->target_num * device->lun_num * device->plane_num *
+ device->block_num;
+}
+
+struct nand_device *nand_get_device(int index);
+#endif /* __NAND_DEVICE_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/core/nfi.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NFI_H__
+#define __NFI_H__
+
+struct nfi_format {
+ int page_size;
+ int spare_size;
+ int ecc_req;
+};
+
+struct nfi {
+ int sector_size;
+ int sector_spare_size;
+ int fdm_size; /*for sector*/
+ int fdm_ecc_size;
+ int ecc_strength;
+ int ecc_parity_size; /*for sector*/
+
+ int (*select_chip)(struct nfi *nfi, int cs);
+ int (*set_format)(struct nfi *nfi, struct nfi_format *format);
+ int (*set_timing)(struct nfi *nfi, void *timing, int type);
+ int (*nfi_ctrl)(struct nfi *nfi, int cmd, void *args);
+
+ int (*reset)(struct nfi *nfi);
+ int (*send_cmd)(struct nfi *nfi, short cmd);
+ int (*send_addr)(struct nfi *nfi, int col, int row,
+ int col_cycle, int row_cycle);
+ int (*trigger)(struct nfi *nfi);
+
+ int (*write_page)(struct nfi *nfi, u8 *data, u8 *fdm);
+ int (*write_bytes)(struct nfi *nfi, u8 *data, int count);
+ int (*read_sectors)(struct nfi *nfi, u8 *data, u8 *fdm,
+ int sectors);
+ int (*read_bytes)(struct nfi *nfi, u8 *data, int count);
+
+ int (*wait_ready)(struct nfi *nfi, int type, u32 timeout);
+
+ int (*enable_randomizer)(struct nfi *nfi, u32 row, bool encode);
+ int (*disable_randomizer)(struct nfi *nfi);
+};
+
+struct nfi *nfi_init(struct nfi_resource *res);
+void nfi_exit(struct nfi *nfi);
+
+#endif /* __NFI_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/core/nfi/nfi_base.c
@@ -0,0 +1,1357 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+/**
+ * nfi_base.c - the base logic for nfi to access nand flash
+ *
+ * slc/mlc/tlc could use same code to access nand
+ * of cause, there still some work need to do
+ * even for spi nand, there should be a chance to integrate code together
+ */
+
+#include "nandx_util.h"
+#include "nandx_core.h"
+#include "../nfi.h"
+#include "../nand_device.h"
+#include "nfi_regs.h"
+#include "nfiecc.h"
+#include "nfi_base.h"
+
+static const int spare_size_mt7622[] = {
+ 16, 26, 27, 28
+};
+
+#define RAND_SEED_SHIFT(op) \
+ ((op) == RAND_ENCODE ? ENCODE_SEED_SHIFT : DECODE_SEED_SHIFT)
+#define RAND_EN(op) \
+ ((op) == RAND_ENCODE ? RAN_ENCODE_EN : RAN_DECODE_EN)
+
+#define SS_SEED_NUM 128
+static u16 ss_randomizer_seed[SS_SEED_NUM] = {
+ 0x576A, 0x05E8, 0x629D, 0x45A3, 0x649C, 0x4BF0, 0x2342, 0x272E,
+ 0x7358, 0x4FF3, 0x73EC, 0x5F70, 0x7A60, 0x1AD8, 0x3472, 0x3612,
+ 0x224F, 0x0454, 0x030E, 0x70A5, 0x7809, 0x2521, 0x484F, 0x5A2D,
+ 0x492A, 0x043D, 0x7F61, 0x3969, 0x517A, 0x3B42, 0x769D, 0x0647,
+ 0x7E2A, 0x1383, 0x49D9, 0x07B8, 0x2578, 0x4EEC, 0x4423, 0x352F,
+ 0x5B22, 0x72B9, 0x367B, 0x24B6, 0x7E8E, 0x2318, 0x6BD0, 0x5519,
+ 0x1783, 0x18A7, 0x7B6E, 0x7602, 0x4B7F, 0x3648, 0x2C53, 0x6B99,
+ 0x0C23, 0x67CF, 0x7E0E, 0x4D8C, 0x5079, 0x209D, 0x244A, 0x747B,
+ 0x350B, 0x0E4D, 0x7004, 0x6AC3, 0x7F3E, 0x21F5, 0x7A15, 0x2379,
+ 0x1517, 0x1ABA, 0x4E77, 0x15A1, 0x04FA, 0x2D61, 0x253A, 0x1302,
+ 0x1F63, 0x5AB3, 0x049A, 0x5AE8, 0x1CD7, 0x4A00, 0x30C8, 0x3247,
+ 0x729C, 0x5034, 0x2B0E, 0x57F2, 0x00E4, 0x575B, 0x6192, 0x38F8,
+ 0x2F6A, 0x0C14, 0x45FC, 0x41DF, 0x38DA, 0x7AE1, 0x7322, 0x62DF,
+ 0x5E39, 0x0E64, 0x6D85, 0x5951, 0x5937, 0x6281, 0x33A1, 0x6A32,
+ 0x3A5A, 0x2BAC, 0x743A, 0x5E74, 0x3B2E, 0x7EC7, 0x4FD2, 0x5D28,
+ 0x751F, 0x3EF8, 0x39B1, 0x4E49, 0x746B, 0x6EF6, 0x44BE, 0x6DB7
+};
+
+#if 0
+static void dump_register(void *regs)
+{
+ int i;
+
+ pr_info("registers:\n");
+ for (i = 0; i < 0x600; i += 0x10) {
+ pr_info(" address 0x%X : %X %X %X %X\n",
+ (u32)((unsigned long)regs + i),
+ (u32)readl(regs + i),
+ (u32)readl(regs + i + 0x4),
+ (u32)readl(regs + i + 0x8),
+ (u32)readl(regs + i + 0xC));
+ }
+}
+#endif
+
+static int nfi_enable_randomizer(struct nfi *nfi, u32 row, bool encode)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ enum randomizer_op op = RAND_ENCODE;
+ void *regs = nb->res.nfi_regs;
+ u32 val;
+
+ if (!encode)
+ op = RAND_DECODE;
+
+ /* randomizer type and reseed type setup */
+ val = readl(regs + NFI_CNFG);
+ val |= CNFG_RAND_SEL | CNFG_RESEED_SEC_EN;
+ writel(val, regs + NFI_CNFG);
+
+ /* randomizer seed and type setup */
+ val = ss_randomizer_seed[row % SS_SEED_NUM] & RAN_SEED_MASK;
+ val <<= RAND_SEED_SHIFT(op);
+ val |= RAND_EN(op);
+ writel(val, regs + NFI_RANDOM_CNFG);
+
+ return 0;
+}
+
+static int nfi_disable_randomizer(struct nfi *nfi)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+
+ writel(0, nb->res.nfi_regs + NFI_RANDOM_CNFG);
+
+ return 0;
+}
+
+static int nfi_irq_handler(int irq, void *data)
+{
+ struct nfi_base *nb = (struct nfi_base *) data;
+ void *regs = nb->res.nfi_regs;
+ u16 status, en;
+
+ status = readw(regs + NFI_INTR_STA);
+ en = readw(regs + NFI_INTR_EN);
+
+ if (!(status & en))
+ return NAND_IRQ_NONE;
+
+ writew(~status & en, regs + NFI_INTR_EN);
+
+ nandx_event_complete(nb->done);
+
+ return NAND_IRQ_HANDLED;
+}
+
+static int nfi_select_chip(struct nfi *nfi, int cs)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+
+ writel(cs, nb->res.nfi_regs + NFI_CSEL);
+
+ return 0;
+}
+
+static inline void set_op_mode(void *regs, u32 mode)
+{
+ u32 val = readl(regs + NFI_CNFG);
+
+ val &= ~CNFG_OP_MODE_MASK;
+ val |= mode;
+
+ writel(val, regs + NFI_CNFG);
+}
+
+static int nfi_reset(struct nfi *nfi)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ void *regs = nb->res.nfi_regs;
+ int ret, val;
+
+ /* The NFI reset to reset all registers and force the NFI
+ * master be early terminated
+ */
+ writel(CON_FIFO_FLUSH | CON_NFI_RST, regs + NFI_CON);
+
+ /* check state of NFI internal FSM and NAND interface FSM */
+ ret = readl_poll_timeout_atomic(regs + NFI_MASTER_STA, val,
+ !(val & MASTER_BUS_BUSY),
+ 10, NFI_TIMEOUT);
+ if (ret)
+ pr_info("nfi reset timeout...\n");
+
+ writel(CON_FIFO_FLUSH | CON_NFI_RST, regs + NFI_CON);
+ writew(STAR_DE, regs + NFI_STRDATA);
+
+ return ret;
+}
+
+static void bad_mark_swap(struct nfi *nfi, u8 *buf, u8 *fdm)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ u32 start_sector = div_down(nb->col, nfi->sector_size);
+ u32 data_mark_pos;
+ u8 temp;
+
+ /* raw access, no need to do swap. */
+ if (!nb->ecc_en)
+ return;
+
+ if (!buf || !fdm)
+ return;
+
+ if (nb->bad_mark_ctrl.sector < start_sector ||
+ nb->bad_mark_ctrl.sector > start_sector + nb->rw_sectors)
+ return;
+
+ data_mark_pos = nb->bad_mark_ctrl.position +
+ (nb->bad_mark_ctrl.sector - start_sector) *
+ nfi->sector_size;
+
+ temp = *fdm;
+ *fdm = *(buf + data_mark_pos);
+ *(buf + data_mark_pos) = temp;
+}
+
+static u8 *fdm_shift(struct nfi *nfi, u8 *fdm, int sector)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ u8 *pos;
+
+ if (!fdm)
+ return NULL;
+
+ /* map the sector's FDM data to free oob:
+ * the beginning of the oob area stores the FDM data of bad mark sectors
+ */
+ if (sector < nb->bad_mark_ctrl.sector)
+ pos = fdm + (sector + 1) * nfi->fdm_size;
+ else if (sector == nb->bad_mark_ctrl.sector)
+ pos = fdm;
+ else
+ pos = fdm + sector * nfi->fdm_size;
+
+ return pos;
+
+}
+
+static void set_bad_mark_ctrl(struct nfi_base *nb)
+{
+ int temp, page_size = nb->format.page_size;
+
+ nb->bad_mark_ctrl.bad_mark_swap = bad_mark_swap;
+ nb->bad_mark_ctrl.fdm_shift = fdm_shift;
+
+ temp = nb->nfi.sector_size + nb->nfi.sector_spare_size;
+ nb->bad_mark_ctrl.sector = div_down(page_size, temp);
+ nb->bad_mark_ctrl.position = reminder(page_size, temp);
+}
+
+/* NOTE: check if page_size valid future */
+static int setup_format(struct nfi_base *nb, int spare_idx)
+{
+ struct nfi *nfi = &nb->nfi;
+ u32 page_size = nb->format.page_size;
+ u32 val;
+
+ switch (page_size) {
+ case 512:
+ val = PAGEFMT_512_2K | PAGEFMT_SEC_SEL_512;
+ break;
+
+ case KB(2):
+ if (nfi->sector_size == 512)
+ val = PAGEFMT_2K_4K | PAGEFMT_SEC_SEL_512;
+ else
+ val = PAGEFMT_512_2K;
+
+ break;
+
+ case KB(4):
+ if (nfi->sector_size == 512)
+ val = PAGEFMT_4K_8K | PAGEFMT_SEC_SEL_512;
+ else
+ val = PAGEFMT_2K_4K;
+
+ break;
+
+ case KB(8):
+ if (nfi->sector_size == 512)
+ val = PAGEFMT_8K_16K | PAGEFMT_SEC_SEL_512;
+ else
+ val = PAGEFMT_4K_8K;
+
+ break;
+
+ case KB(16):
+ val = PAGEFMT_8K_16K;
+ break;
+
+ default:
+ pr_info("invalid page len: %d\n", page_size);
+ return -EINVAL;
+ }
+
+ val |= spare_idx << PAGEFMT_SPARE_SHIFT;
+ val |= nfi->fdm_size << PAGEFMT_FDM_SHIFT;
+ val |= nfi->fdm_ecc_size << PAGEFMT_FDM_ECC_SHIFT;
+ writel(val, nb->res.nfi_regs + NFI_PAGEFMT);
+
+ if (nb->custom_sector_en) {
+ val = nfi->sector_spare_size + nfi->sector_size;
+ val |= SECCUS_SIZE_EN;
+ writel(val, nb->res.nfi_regs + NFI_SECCUS_SIZE);
+ }
+
+ return 0;
+}
+
+static int adjust_spare(struct nfi_base *nb, int *spare)
+{
+ int multi = nb->nfi.sector_size == 512 ? 1 : 2;
+ int i, count = nb->caps->spare_size_num;
+
+ if (*spare >= nb->caps->spare_size[count - 1] * multi) {
+ *spare = nb->caps->spare_size[count - 1] * multi;
+ return count - 1;
+ }
+
+ if (*spare < nb->caps->spare_size[0] * multi)
+ return -EINVAL;
+
+ for (i = 1; i < count; i++) {
+ if (*spare < nb->caps->spare_size[i] * multi) {
+ *spare = nb->caps->spare_size[i - 1] * multi;
+ return i - 1;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int nfi_set_format(struct nfi *nfi, struct nfi_format *format)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ struct nfiecc *ecc = nb->ecc;
+ int ecc_strength = format->ecc_req;
+ int min_fdm, min_ecc, max_ecc;
+ u32 temp, page_sectors;
+ int spare_idx = 0;
+
+ if (!nb->buf) {
+#if NANDX_BULK_IO_USE_DRAM
+ nb->buf = NANDX_NFI_BUF_ADDR;
+#else
+ nb->buf = mem_alloc(1, format->page_size + format->spare_size);
+#endif
+ if (!nb->buf)
+ return -ENOMEM;
+ }
+
+ nb->format = *format;
+
+ /* ToBeFixed: for spi nand, now sector size is 512,
+ * it should be same with slc.
+ */
+ nfi->sector_size = 512;
+ /* format->ecc_req is the requirement per 1KB */
+ ecc_strength >>= 1;
+
+ page_sectors = div_down(format->page_size, nfi->sector_size);
+ nfi->sector_spare_size = div_down(format->spare_size, page_sectors);
+
+ if (!nb->custom_sector_en) {
+ spare_idx = adjust_spare(nb, &nfi->sector_spare_size);
+ if (spare_idx < 0)
+ return -EINVAL;
+ }
+
+ /* calculate ecc strength and fdm size */
+ temp = (nfi->sector_spare_size - nb->caps->max_fdm_size) * 8;
+ min_ecc = div_down(temp, nb->caps->ecc_parity_bits);
+ min_ecc = ecc->adjust_strength(ecc, min_ecc);
+ if (min_ecc < 0)
+ return -EINVAL;
+
+ temp = div_up(nb->res.min_oob_req, page_sectors);
+ temp = (nfi->sector_spare_size - temp) * 8;
+ max_ecc = div_down(temp, nb->caps->ecc_parity_bits);
+ max_ecc = ecc->adjust_strength(ecc, max_ecc);
+ if (max_ecc < 0)
+ return -EINVAL;
+
+ temp = div_up(temp * nb->caps->ecc_parity_bits, 8);
+ temp = nfi->sector_spare_size - temp;
+ min_fdm = min_t(u32, temp, (u32)nb->caps->max_fdm_size);
+
+ if (ecc_strength > max_ecc) {
+ pr_info("required ecc strength %d, max supported %d\n",
+ ecc_strength, max_ecc);
+ nfi->ecc_strength = max_ecc;
+ nfi->fdm_size = min_fdm;
+ } else if (format->ecc_req < min_ecc) {
+ nfi->ecc_strength = min_ecc;
+ nfi->fdm_size = nb->caps->max_fdm_size;
+ } else {
+ ecc_strength = ecc->adjust_strength(ecc, ecc_strength);
+ if (ecc_strength < 0)
+ return -EINVAL;
+
+ nfi->ecc_strength = ecc_strength;
+ temp = div_up(ecc_strength * nb->caps->ecc_parity_bits, 8);
+ nfi->fdm_size = nfi->sector_spare_size - temp;
+ }
+
+ nb->page_sectors = div_down(format->page_size, nfi->sector_size);
+
+ /* some IC has fixed fdm_ecc_size, if not assigend, set to fdm_size */
+ nfi->fdm_ecc_size = nb->caps->fdm_ecc_size ? : nfi->fdm_size;
+
+ nfi->ecc_parity_size = div_up(nfi->ecc_strength *
+ nb->caps->ecc_parity_bits,
+ 8);
+ set_bad_mark_ctrl(nb);
+
+ pr_debug("sector_size: %d\n", nfi->sector_size);
+ pr_debug("sector_spare_size: %d\n", nfi->sector_spare_size);
+ pr_debug("fdm_size: %d\n", nfi->fdm_size);
+ pr_debug("fdm_ecc_size: %d\n", nfi->fdm_ecc_size);
+ pr_debug("ecc_strength: %d\n", nfi->ecc_strength);
+ pr_debug("ecc_parity_size: %d\n", nfi->ecc_parity_size);
+
+ return setup_format(nb, spare_idx);
+}
+
+static int nfi_ctrl(struct nfi *nfi, int cmd, void *args)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ int ret = 0;
+
+ switch (cmd) {
+ case NFI_CTRL_DMA:
+ nb->dma_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_AUTOFORMAT:
+ nb->auto_format = *(bool *)args;
+ break;
+
+ case NFI_CTRL_NFI_IRQ:
+ nb->nfi_irq_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_PAGE_IRQ:
+ nb->page_irq_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_BAD_MARK_SWAP:
+ nb->bad_mark_swap_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_ECC:
+ nb->ecc_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_ECC_MODE:
+ nb->ecc_mode = *(enum nfiecc_mode *)args;
+ break;
+
+ case NFI_CTRL_ECC_CLOCK:
+ /* NOTE: it seems that there's nothing need to do
+ * if new IC need, just add tht logic
+ */
+ nb->ecc_clk_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_ECC_IRQ:
+ nb->ecc_irq_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_ECC_DECODE_MODE:
+ nb->ecc_deccon = *(enum nfiecc_deccon *)args;
+ break;
+
+ default:
+ pr_info("invalid arguments.\n");
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ pr_debug("%s: set cmd(%d) to %d\n", __func__, cmd, *(int *)args);
+ return ret;
+}
+
+static int nfi_send_cmd(struct nfi *nfi, short cmd)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ void *regs = nb->res.nfi_regs;
+ int ret;
+ u32 val;
+
+ pr_debug("%s: cmd 0x%x\n", __func__, cmd);
+
+ if (cmd < 0)
+ return -EINVAL;
+
+ set_op_mode(regs, nb->op_mode);
+
+ writel(cmd, regs + NFI_CMD);
+
+ ret = readl_poll_timeout_atomic(regs + NFI_STA,
+ val, !(val & STA_CMD),
+ 5, NFI_TIMEOUT);
+ if (ret)
+ pr_info("send cmd 0x%x timeout\n", cmd);
+
+ return ret;
+}
+
+static int nfi_send_addr(struct nfi *nfi, int col, int row,
+ int col_cycle, int row_cycle)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ void *regs = nb->res.nfi_regs;
+ int ret;
+ u32 val;
+
+ pr_debug("%s: col 0x%x, row 0x%x, col_cycle 0x%x, row_cycle 0x%x\n",
+ __func__, col, row, col_cycle, row_cycle);
+
+ nb->col = col;
+ nb->row = row;
+
+ writel(col, regs + NFI_COLADDR);
+ writel(row, regs + NFI_ROWADDR);
+ writel(col_cycle | (row_cycle << ROW_SHIFT), regs + NFI_ADDRNOB);
+
+ ret = readl_poll_timeout_atomic(regs + NFI_STA,
+ val, !(val & STA_ADDR),
+ 5, NFI_TIMEOUT);
+ if (ret)
+ pr_info("send address timeout\n");
+
+ return ret;
+}
+
+static int nfi_trigger(struct nfi *nfi)
+{
+ /* Nothing need to do. */
+ return 0;
+}
+
+static inline int wait_io_ready(void *regs)
+{
+ u32 val;
+ int ret;
+
+ ret = readl_poll_timeout_atomic(regs + NFI_PIO_DIRDY,
+ val, val & PIO_DI_RDY,
+ 2, NFI_TIMEOUT);
+ if (ret)
+ pr_info("wait io ready timeout\n");
+
+ return ret;
+}
+
+static int wait_ready_irq(struct nfi_base *nb, u32 timeout)
+{
+ void *regs = nb->res.nfi_regs;
+ int ret;
+ u32 val;
+
+ writel(0xf1, regs + NFI_CNRNB);
+ nandx_event_init(nb->done);
+
+ writel(INTR_BUSY_RETURN_EN, (void *)(regs + NFI_INTR_EN));
+
+ /**
+ * check if nand already bean ready,
+ * avoid issue that casued by missing irq-event.
+ */
+ val = readl(regs + NFI_STA);
+ if (val & STA_BUSY2READY) {
+ readl(regs + NFI_INTR_STA);
+ writel(0, (void *)(regs + NFI_INTR_EN));
+ return 0;
+ }
+
+ ret = nandx_event_wait_complete(nb->done, timeout);
+
+ writew(0, regs + NFI_CNRNB);
+ return ret;
+}
+
+static void wait_ready_twhr2(struct nfi_base *nb, u32 timeout)
+{
+ /* NOTE: this for tlc */
+}
+
+static int wait_ready_poll(struct nfi_base *nb, u32 timeout)
+{
+ void *regs = nb->res.nfi_regs;
+ int ret;
+ u32 val;
+
+ writel(0x21, regs + NFI_CNRNB);
+ ret = readl_poll_timeout_atomic(regs + NFI_STA, val,
+ val & STA_BUSY2READY,
+ 2, timeout);
+ writew(0, regs + NFI_CNRNB);
+
+ return ret;
+}
+
+static int nfi_wait_ready(struct nfi *nfi, int type, u32 timeout)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ int ret;
+
+ switch (type) {
+ case NAND_WAIT_IRQ:
+ if (nb->nfi_irq_en)
+ ret = wait_ready_irq(nb, timeout);
+ else
+ ret = -EINVAL;
+
+ break;
+
+ case NAND_WAIT_POLLING:
+ ret = wait_ready_poll(nb, timeout);
+ break;
+
+ case NAND_WAIT_TWHR2:
+ wait_ready_twhr2(nb, timeout);
+ ret = 0;
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret)
+ pr_info("%s: type 0x%x, timeout 0x%x\n",
+ __func__, type, timeout);
+
+ return ret;
+}
+
+static int enable_ecc_decode(struct nfi_base *nb, int sectors)
+{
+ struct nfi *nfi = &nb->nfi;
+ struct nfiecc *ecc = nb->ecc;
+
+ ecc->config.op = ECC_DECODE;
+ ecc->config.mode = nb->ecc_mode;
+ ecc->config.deccon = nb->ecc_deccon;
+ ecc->config.sectors = sectors;
+ ecc->config.len = nfi->sector_size + nfi->fdm_ecc_size;
+ ecc->config.strength = nfi->ecc_strength;
+
+ return ecc->enable(ecc);
+}
+
+static int enable_ecc_encode(struct nfi_base *nb)
+{
+ struct nfiecc *ecc = nb->ecc;
+ struct nfi *nfi = &nb->nfi;
+
+ ecc->config.op = ECC_ENCODE;
+ ecc->config.mode = nb->ecc_mode;
+ ecc->config.len = nfi->sector_size + nfi->fdm_ecc_size;
+ ecc->config.strength = nfi->ecc_strength;
+
+ return ecc->enable(ecc);
+}
+
+static void read_fdm(struct nfi_base *nb, u8 *fdm, int start_sector,
+ int sectors)
+{
+ void *regs = nb->res.nfi_regs;
+ int j, i = start_sector;
+ u32 vall, valm;
+ u8 *buf = fdm;
+
+ for (; i < start_sector + sectors; i++) {
+ if (nb->bad_mark_swap_en)
+ buf = nb->bad_mark_ctrl.fdm_shift(&nb->nfi, fdm, i);
+
+ vall = readl(regs + NFI_FDML(i));
+ valm = readl(regs + NFI_FDMM(i));
+
+ for (j = 0; j < nb->nfi.fdm_size; j++)
+ *buf++ = (j >= 4 ? valm : vall) >> ((j & 3) << 3);
+ }
+}
+
+static void write_fdm(struct nfi_base *nb, u8 *fdm)
+{
+ struct nfi *nfi = &nb->nfi;
+ void *regs = nb->res.nfi_regs;
+ u32 vall, valm;
+ int i, j;
+ u8 *buf = fdm;
+
+ for (i = 0; i < nb->page_sectors; i++) {
+ if (nb->bad_mark_swap_en)
+ buf = nb->bad_mark_ctrl.fdm_shift(nfi, fdm, i);
+
+ vall = 0;
+ for (j = 0; j < 4; j++)
+ vall |= (j < nfi->fdm_size ? *buf++ : 0xff) << (j * 8);
+ writel(vall, regs + NFI_FDML(i));
+
+ valm = 0;
+ for (j = 0; j < 4; j++)
+ valm |= (j < nfi->fdm_size ? *buf++ : 0xff) << (j * 8);
+ writel(valm, regs + NFI_FDMM(i));
+ }
+}
+
+/* NOTE: pio not use auto format */
+static int pio_rx_data(struct nfi_base *nb, u8 *data, u8 *fdm,
+ int sectors)
+{
+ struct nfiecc_status ecc_status;
+ struct nfi *nfi = &nb->nfi;
+ void *regs = nb->res.nfi_regs;
+ u32 val, bitflips = 0;
+ int len, ret, i;
+ u8 *buf;
+
+ val = readl(regs + NFI_CNFG) | CNFG_BYTE_RW;
+ writel(val, regs + NFI_CNFG);
+
+ len = nfi->sector_size + nfi->sector_spare_size;
+ len *= sectors;
+
+ for (i = 0; i < len; i++) {
+ ret = wait_io_ready(regs);
+ if (ret)
+ return ret;
+
+ nb->buf[i] = readb(regs + NFI_DATAR);
+ }
+
+ /* TODO: do error handle for autoformat setting of pio */
+ if (nb->ecc_en) {
+ for (i = 0; i < sectors; i++) {
+ buf = nb->buf + i * (nfi->sector_size +
+ nfi->sector_spare_size);
+ ret = nb->ecc->correct_data(nb->ecc, &ecc_status,
+ buf, i);
+ if (data)
+ memcpy(data + i * nfi->sector_size,
+ buf, nfi->sector_size);
+ if (fdm)
+ memcpy(fdm + i * nfi->fdm_size,
+ buf + nfi->sector_size, nfi->fdm_size);
+ if (ret) {
+ ret = nb->ecc->decode_status(nb->ecc, i, 1);
+ if (ret < 0)
+ return ret;
+
+ bitflips = max_t(int, (int)bitflips, ret);
+ }
+ }
+
+ return bitflips;
+ }
+
+ /* raw read, only data not null, and its length should be $len */
+ if (data)
+ memcpy(data, nb->buf, len);
+
+ return 0;
+}
+
+static int pio_tx_data(struct nfi_base *nb, u8 *data, u8 *fdm,
+ int sectors)
+{
+ struct nfi *nfi = &nb->nfi;
+ void *regs = nb->res.nfi_regs;
+ u32 i, val;
+ int len, ret;
+
+ val = readw(regs + NFI_CNFG) | CNFG_BYTE_RW;
+ writew(val, regs + NFI_CNFG);
+
+ len = nb->ecc_en ? nfi->sector_size :
+ nfi->sector_size + nfi->sector_spare_size;
+ len *= sectors;
+
+ /* data shouldn't null,
+ * and if ecc enable ,fdm been written in prepare process
+ */
+ for (i = 0; i < len; i++) {
+ ret = wait_io_ready(regs);
+ if (ret)
+ return ret;
+ writeb(data[i], regs + NFI_DATAW);
+ }
+
+ return 0;
+}
+
+static bool is_page_empty(struct nfi_base *nb, u8 *data, u8 *fdm,
+ int sectors)
+{
+ u32 empty = readl(nb->res.nfi_regs + NFI_STA) & STA_EMP_PAGE;
+
+ if (empty) {
+ pr_info("empty page!\n");
+ return true;
+ }
+
+ return false;
+}
+
+static int rw_prepare(struct nfi_base *nb, int sectors, u8 *data,
+ u8 *fdm, bool read)
+{
+ void *regs = nb->res.nfi_regs;
+ u32 len = nb->nfi.sector_size * sectors;
+ bool irq_en = nb->dma_en && nb->nfi_irq_en;
+ void *dma_addr;
+ u32 val;
+ int ret;
+
+ nb->rw_sectors = sectors;
+
+ if (irq_en) {
+ nandx_event_init(nb->done);
+ writel(INTR_AHB_DONE_EN, regs + NFI_INTR_EN);
+ }
+
+ val = readw(regs + NFI_CNFG);
+ if (read)
+ val |= CNFG_READ_EN;
+ else
+ val &= ~CNFG_READ_EN;
+
+ /* as design, now, auto format enabled when ecc enabled */
+ if (nb->ecc_en) {
+ val |= CNFG_HW_ECC_EN | CNFG_AUTO_FMT_EN;
+
+ if (read)
+ ret = enable_ecc_decode(nb, sectors);
+ else
+ ret = enable_ecc_encode(nb);
+
+ if (ret) {
+ pr_info("%s: ecc enable %s fail!\n", __func__,
+ read ? "decode" : "encode");
+ return ret;
+ }
+ } else {
+ val &= ~(CNFG_HW_ECC_EN | CNFG_AUTO_FMT_EN);
+ }
+
+ if (!read && nb->bad_mark_swap_en)
+ nb->bad_mark_ctrl.bad_mark_swap(&nb->nfi, data, fdm);
+
+ if (!nb->ecc_en && read)
+ len += sectors * nb->nfi.sector_spare_size;
+
+ if (nb->dma_en) {
+ val |= CNFG_DMA_BURST_EN | CNFG_AHB;
+
+ if (read) {
+ dma_addr = (void *)(unsigned long)nandx_dma_map(
+ nb->res.dev, nb->buf,
+ (u64)len, NDMA_FROM_DEV);
+ } else {
+ memcpy(nb->buf, data, len);
+ dma_addr = (void *)(unsigned long)nandx_dma_map(
+ nb->res.dev, nb->buf,
+ (u64)len, NDMA_TO_DEV);
+ }
+
+ writel((unsigned long)dma_addr, (void *)regs + NFI_STRADDR);
+
+ nb->access_len = len;
+ nb->dma_addr = dma_addr;
+ }
+
+ if (nb->ecc_en && !read && fdm)
+ write_fdm(nb, fdm);
+
+ writew(val, regs + NFI_CNFG);
+ /* setup R/W sector number */
+ writel(sectors << CON_SEC_SHIFT, regs + NFI_CON);
+
+ return 0;
+}
+
+static void rw_trigger(struct nfi_base *nb, bool read)
+{
+ void *regs = nb->res.nfi_regs;
+ u32 val;
+
+ val = read ? CON_BRD : CON_BWR;
+ val |= readl(regs + NFI_CON);
+ writel(val, regs + NFI_CON);
+
+ writel(STAR_EN, regs + NFI_STRDATA);
+}
+
+static int rw_wait_done(struct nfi_base *nb, int sectors, bool read)
+{
+ void *regs = nb->res.nfi_regs;
+ bool irq_en = nb->dma_en && nb->nfi_irq_en;
+ int ret;
+ u32 val;
+
+ if (irq_en) {
+ ret = nandx_event_wait_complete(nb->done, NFI_TIMEOUT);
+ if (!ret) {
+ writew(0, regs + NFI_INTR_EN);
+ return ret;
+ }
+ }
+
+ if (read) {
+ ret = readl_poll_timeout_atomic(regs + NFI_BYTELEN, val,
+ ADDRCNTR_SEC(val) >=
+ (u32)sectors,
+ 2, NFI_TIMEOUT);
+ /* HW issue: if not wait ahb done, need polling bus busy */
+ if (!ret && !irq_en)
+ ret = readl_poll_timeout_atomic(regs + NFI_MASTER_STA,
+ val,
+ !(val &
+ MASTER_BUS_BUSY),
+ 2, NFI_TIMEOUT);
+ } else {
+ ret = readl_poll_timeout_atomic(regs + NFI_ADDRCNTR, val,
+ ADDRCNTR_SEC(val) >=
+ (u32)sectors,
+ 2, NFI_TIMEOUT);
+ }
+
+ if (ret) {
+ pr_info("do page %s timeout\n", read ? "read" : "write");
+ return ret;
+ }
+
+ if (read && nb->ecc_en) {
+ ret = nb->ecc->wait_done(nb->ecc);
+ if (ret)
+ return ret;
+
+ return nb->ecc->decode_status(nb->ecc, 0, sectors);
+ }
+
+ return 0;
+}
+
+static int rw_data(struct nfi_base *nb, u8 *data, u8 *fdm, int sectors,
+ bool read)
+{
+ if (read && nb->dma_en && nb->ecc_en && fdm)
+ read_fdm(nb, fdm, 0, sectors);
+
+ if (!nb->dma_en) {
+ if (read)
+ return pio_rx_data(nb, data, fdm, sectors);
+
+ return pio_tx_data(nb, data, fdm, sectors);
+ }
+
+ return 0;
+}
+
+static void rw_complete(struct nfi_base *nb, u8 *data, u8 *fdm,
+ bool read)
+{
+ int data_len = 0;
+ bool is_empty;
+
+ if (nb->dma_en) {
+ if (read) {
+ nandx_dma_unmap(nb->res.dev, nb->buf, nb->dma_addr,
+ (u64)nb->access_len, NDMA_FROM_DEV);
+
+ if (data) {
+ data_len = nb->rw_sectors * nb->nfi.sector_size;
+ memcpy(data, nb->buf, data_len);
+ }
+
+ if (fdm)
+ memcpy(fdm, nb->buf + data_len,
+ nb->access_len - data_len);
+
+ if (nb->read_status == -ENANDREAD) {
+ is_empty = nb->is_page_empty(nb, data, fdm,
+ nb->rw_sectors);
+ if (is_empty)
+ nb->read_status = 0;
+ }
+ } else {
+ nandx_dma_unmap(nb->res.dev, nb->buf, nb->dma_addr,
+ (u64)nb->access_len, NDMA_TO_DEV);
+ }
+ }
+
+ /* whether it's reading or writing, we all check if nee swap
+ * for write, we need to restore data
+ */
+ if (nb->bad_mark_swap_en)
+ nb->bad_mark_ctrl.bad_mark_swap(&nb->nfi, data, fdm);
+
+ if (nb->ecc_en)
+ nb->ecc->disable(nb->ecc);
+
+ writel(0, nb->res.nfi_regs + NFI_CNFG);
+ writel(0, nb->res.nfi_regs + NFI_CON);
+}
+
+static int nfi_read_sectors(struct nfi *nfi, u8 *data, u8 *fdm,
+ int sectors)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ int bitflips = 0, ret;
+
+ pr_debug("%s: read page#%d\n", __func__, nb->row);
+ pr_debug("%s: data address 0x%x, fdm address 0x%x, sectors 0x%x\n",
+ __func__, (u32)((unsigned long)data),
+ (u32)((unsigned long)fdm), sectors);
+
+ nb->read_status = 0;
+
+ ret = nb->rw_prepare(nb, sectors, data, fdm, true);
+ if (ret)
+ return ret;
+
+ nb->rw_trigger(nb, true);
+
+ if (nb->dma_en) {
+ ret = nb->rw_wait_done(nb, sectors, true);
+ if (ret > 0)
+ bitflips = ret;
+ else if (ret == -ENANDREAD)
+ nb->read_status = -ENANDREAD;
+ else if (ret < 0)
+ goto complete;
+
+ }
+
+ ret = nb->rw_data(nb, data, fdm, sectors, true);
+ if (ret > 0)
+ ret = max_t(int, ret, bitflips);
+
+complete:
+ nb->rw_complete(nb, data, fdm, true);
+
+ if (nb->read_status == -ENANDREAD)
+ return -ENANDREAD;
+
+ return ret;
+}
+
+int nfi_write_page(struct nfi *nfi, u8 *data, u8 *fdm)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ u32 sectors = div_down(nb->format.page_size, nfi->sector_size);
+ int ret;
+
+ pr_debug("%s: data address 0x%x, fdm address 0x%x\n",
+ __func__, (int)((unsigned long)data),
+ (int)((unsigned long)fdm));
+
+ ret = nb->rw_prepare(nb, sectors, data, fdm, false);
+ if (ret)
+ return ret;
+
+ nb->rw_trigger(nb, false);
+
+ ret = nb->rw_data(nb, data, fdm, sectors, false);
+ if (ret)
+ return ret;
+
+ ret = nb->rw_wait_done(nb, sectors, false);
+
+ nb->rw_complete(nb, data, fdm, false);
+
+ return ret;
+}
+
+static int nfi_rw_bytes(struct nfi *nfi, u8 *data, int count, bool read)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ void *regs = nb->res.nfi_regs;
+ int i, ret;
+ u32 val;
+
+ for (i = 0; i < count; i++) {
+ val = readl(regs + NFI_STA) & NFI_FSM_MASK;
+ if (val != NFI_FSM_CUSTDATA) {
+ val = readw(regs + NFI_CNFG) | CNFG_BYTE_RW;
+ if (read)
+ val |= CNFG_READ_EN;
+ writew(val, regs + NFI_CNFG);
+
+ val = div_up(count, nfi->sector_size);
+ val = (val << CON_SEC_SHIFT) | CON_BRD | CON_BWR;
+ writel(val, regs + NFI_CON);
+
+ writew(STAR_EN, regs + NFI_STRDATA);
+ }
+
+ ret = wait_io_ready(regs);
+ if (ret)
+ return ret;
+
+ if (read)
+ data[i] = readb(regs + NFI_DATAR);
+ else
+ writeb(data[i], regs + NFI_DATAW);
+ }
+
+ writel(0, nb->res.nfi_regs + NFI_CNFG);
+
+ return 0;
+}
+
+static int nfi_read_bytes(struct nfi *nfi, u8 *data, int count)
+{
+ return nfi_rw_bytes(nfi, data, count, true);
+}
+
+static int nfi_write_bytes(struct nfi *nfi, u8 *data, int count)
+{
+ return nfi_rw_bytes(nfi, data, count, false);
+}
+
+/* As register map says, only when flash macro is idle,
+ * sw reset or nand interface change can be issued
+ */
+static inline int wait_flash_macro_idle(void *regs)
+{
+ u32 val;
+
+ return readl_poll_timeout_atomic(regs + NFI_STA, val,
+ val & FLASH_MACRO_IDLE, 2,
+ NFI_TIMEOUT);
+}
+
+#define ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt) \
+ ((tpoecs) << 28 | (tprecs) << 22 | (tc2r) << 16 | \
+ (tw2r) << 12 | (twh) << 8 | (twst) << 4 | (trlt))
+
+static int nfi_set_sdr_timing(struct nfi *nfi, void *timing, u8 type)
+{
+ struct nand_sdr_timing *sdr = (struct nand_sdr_timing *) timing;
+ struct nfi_base *nb = nfi_to_base(nfi);
+ void *regs = nb->res.nfi_regs;
+ u32 tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt, tstrobe;
+ u32 rate, val;
+ int ret;
+
+ ret = wait_flash_macro_idle(regs);
+ if (ret)
+ return ret;
+
+ /* turn clock rate into KHZ */
+ rate = nb->res.clock_1x / 1000;
+
+ tpoecs = max_t(u16, sdr->tALH, sdr->tCLH);
+ tpoecs = div_up(tpoecs * rate, 1000000);
+ tpoecs &= 0xf;
+
+ tprecs = max_t(u16, sdr->tCLS, sdr->tALS);
+ tprecs = div_up(tprecs * rate, 1000000);
+ tprecs &= 0x3f;
+
+ /* tc2r is in unit of 2T */
+ tc2r = div_up(sdr->tCR * rate, 1000000);
+ tc2r = div_down(tc2r, 2);
+ tc2r &= 0x3f;
+
+ tw2r = div_up(sdr->tWHR * rate, 1000000);
+ tw2r = div_down(tw2r, 2);
+ tw2r &= 0xf;
+
+ twh = max_t(u16, sdr->tREH, sdr->tWH);
+ twh = div_up(twh * rate, 1000000) - 1;
+ twh &= 0xf;
+
+ twst = div_up(sdr->tWP * rate, 1000000) - 1;
+ twst &= 0xf;
+
+ trlt = div_up(sdr->tRP * rate, 1000000) - 1;
+ trlt &= 0xf;
+
+ /* If tREA is bigger than tRP, setup strobe sel here */
+ if ((trlt + 1) * 1000000 / rate < sdr->tREA) {
+ tstrobe = sdr->tREA - (trlt + 1) * 1000000 / rate;
+ tstrobe = div_up(tstrobe * rate, 1000000);
+ val = readl(regs + NFI_DEBUG_CON1);
+ val &= ~STROBE_MASK;
+ val |= tstrobe << STROBE_SHIFT;
+ writel(val, regs + NFI_DEBUG_CON1);
+ }
+
+ /*
+ * ACCON: access timing control register
+ * -------------------------------------
+ * 31:28: tpoecs, minimum required time for CS post pulling down after
+ * accessing the device
+ * 27:22: tprecs, minimum required time for CS pre pulling down before
+ * accessing the device
+ * 21:16: tc2r, minimum required time from NCEB low to NREB low
+ * 15:12: tw2r, minimum required time from NWEB high to NREB low.
+ * 11:08: twh, write enable hold time
+ * 07:04: twst, write wait states
+ * 03:00: trlt, read wait states
+ */
+ val = ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt);
+ pr_info("acctiming: 0x%x\n", val);
+ writel(val, regs + NFI_ACCCON);
+
+ /* set NAND type */
+ writel(NAND_TYPE_ASYNC, regs + NFI_NAND_TYPE_CNFG);
+
+ return ret;
+}
+
+static int nfi_set_timing(struct nfi *nfi, void *timing, int type)
+{
+ switch (type) {
+ case NAND_TIMING_SDR:
+ return nfi_set_sdr_timing(nfi, timing, type);
+
+ /* NOTE: for mlc/tlc */
+ case NAND_TIMING_SYNC_DDR:
+ case NAND_TIMING_TOGGLE_DDR:
+ case NAND_TIMING_NVDDR2:
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void set_nfi_funcs(struct nfi *nfi)
+{
+ nfi->select_chip = nfi_select_chip;
+ nfi->set_format = nfi_set_format;
+ nfi->nfi_ctrl = nfi_ctrl;
+ nfi->set_timing = nfi_set_timing;
+
+ nfi->reset = nfi_reset;
+ nfi->send_cmd = nfi_send_cmd;
+ nfi->send_addr = nfi_send_addr;
+ nfi->trigger = nfi_trigger;
+
+ nfi->write_page = nfi_write_page;
+ nfi->write_bytes = nfi_write_bytes;
+ nfi->read_sectors = nfi_read_sectors;
+ nfi->read_bytes = nfi_read_bytes;
+
+ nfi->wait_ready = nfi_wait_ready;
+
+ nfi->enable_randomizer = nfi_enable_randomizer;
+ nfi->disable_randomizer = nfi_disable_randomizer;
+}
+
+static struct nfi_caps nfi_caps_mt7622 = {
+ .max_fdm_size = 8,
+ .fdm_ecc_size = 1,
+ .ecc_parity_bits = 13,
+ .spare_size = spare_size_mt7622,
+ .spare_size_num = 4,
+};
+
+static struct nfi_caps *nfi_get_match_data(enum mtk_ic_version ic)
+{
+ /* NOTE: add other IC's data */
+ return &nfi_caps_mt7622;
+}
+
+static void set_nfi_base_params(struct nfi_base *nb)
+{
+ nb->ecc_en = false;
+ nb->dma_en = false;
+ nb->nfi_irq_en = false;
+ nb->ecc_irq_en = false;
+ nb->page_irq_en = false;
+ nb->ecc_clk_en = false;
+ nb->randomize_en = false;
+ nb->custom_sector_en = false;
+ nb->bad_mark_swap_en = false;
+
+ nb->op_mode = CNFG_CUSTOM_MODE;
+ nb->ecc_deccon = ECC_DEC_CORRECT;
+ nb->ecc_mode = ECC_NFI_MODE;
+
+ nb->done = nandx_event_create();
+ nb->caps = nfi_get_match_data(nb->res.ic_ver);
+
+ nb->set_op_mode = set_op_mode;
+ nb->is_page_empty = is_page_empty;
+
+ nb->rw_prepare = rw_prepare;
+ nb->rw_trigger = rw_trigger;
+ nb->rw_wait_done = rw_wait_done;
+ nb->rw_data = rw_data;
+ nb->rw_complete = rw_complete;
+}
+
+struct nfi *__weak nfi_extend_init(struct nfi_base *nb)
+{
+ return &nb->nfi;
+}
+
+void __weak nfi_extend_exit(struct nfi_base *nb)
+{
+ mem_free(nb);
+}
+
+struct nfi *nfi_init(struct nfi_resource *res)
+{
+ struct nfiecc_resource ecc_res;
+ struct nfi_base *nb;
+ struct nfiecc *ecc;
+ struct nfi *nfi;
+ int ret;
+
+ nb = mem_alloc(1, sizeof(struct nfi_base));
+ if (!nb) {
+ pr_info("nfi alloc memory fail @%s.\n", __func__);
+ return NULL;
+ }
+
+ nb->res = *res;
+
+ ret = nandx_irq_register(res->dev, res->nfi_irq_id, nfi_irq_handler,
+ "mtk_nand", nb);
+ if (ret) {
+ pr_info("nfi irq register failed!\n");
+ goto error;
+ }
+
+ /* fill ecc paras and init ecc */
+ ecc_res.ic_ver = nb->res.ic_ver;
+ ecc_res.dev = nb->res.dev;
+ ecc_res.irq_id = nb->res.ecc_irq_id;
+ ecc_res.regs = nb->res.ecc_regs;
+ ecc = nfiecc_init(&ecc_res);
+ if (!ecc) {
+ pr_info("nfiecc init fail.\n");
+ return NULL;
+ }
+
+ nb->ecc = ecc;
+
+ set_nfi_base_params(nb);
+ set_nfi_funcs(&nb->nfi);
+
+ /* Assign a temp sector size for reading ID & para page.
+ * We may assign new value later.
+ */
+ nb->nfi.sector_size = 512;
+
+ /* give a default timing, and as discuss
+ * this is the only thing what we need do for nfi init
+ * if need do more, then we can add a function
+ */
+ writel(0x30C77FFF, nb->res.nfi_regs + NFI_ACCCON);
+
+ nfi = nfi_extend_init(nb);
+ if (nfi)
+ return nfi;
+
+error:
+ mem_free(nb);
+ return NULL;
+}
+
+void nfi_exit(struct nfi *nfi)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+
+ nandx_event_destroy(nb->done);
+ nfiecc_exit(nb->ecc);
+#if !NANDX_BULK_IO_USE_DRAM
+ mem_free(nb->buf);
+#endif
+ nfi_extend_exit(nb);
+}
+
--- /dev/null
+++ b/drivers/mtd/nandx/core/nfi/nfi_base.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NFI_BASE_H__
+#define __NFI_BASE_H__
+
+#define NFI_TIMEOUT 1000000
+
+enum randomizer_op {
+ RAND_ENCODE,
+ RAND_DECODE
+};
+
+struct bad_mark_ctrl {
+ void (*bad_mark_swap)(struct nfi *nfi, u8 *buf, u8 *fdm);
+ u8 *(*fdm_shift)(struct nfi *nfi, u8 *fdm, int sector);
+ u32 sector;
+ u32 position;
+};
+
+struct nfi_caps {
+ u8 max_fdm_size;
+ u8 fdm_ecc_size;
+ u8 ecc_parity_bits;
+ const int *spare_size;
+ u32 spare_size_num;
+};
+
+struct nfi_base {
+ struct nfi nfi;
+ struct nfi_resource res;
+ struct nfiecc *ecc;
+ struct nfi_format format;
+ struct nfi_caps *caps;
+ struct bad_mark_ctrl bad_mark_ctrl;
+
+ /* page_size + spare_size */
+ u8 *buf;
+
+ /* used for spi nand */
+ u8 cmd_mode;
+ u32 op_mode;
+
+ int page_sectors;
+
+ void *done;
+
+ /* for read/write */
+ int col;
+ int row;
+ int access_len;
+ int rw_sectors;
+ void *dma_addr;
+ int read_status;
+
+ bool dma_en;
+ bool nfi_irq_en;
+ bool page_irq_en;
+ bool auto_format;
+ bool ecc_en;
+ bool ecc_irq_en;
+ bool ecc_clk_en;
+ bool randomize_en;
+ bool custom_sector_en;
+ bool bad_mark_swap_en;
+
+ enum nfiecc_deccon ecc_deccon;
+ enum nfiecc_mode ecc_mode;
+
+ void (*set_op_mode)(void *regs, u32 mode);
+ bool (*is_page_empty)(struct nfi_base *nb, u8 *data, u8 *fdm,
+ int sectors);
+
+ int (*rw_prepare)(struct nfi_base *nb, int sectors, u8 *data, u8 *fdm,
+ bool read);
+ void (*rw_trigger)(struct nfi_base *nb, bool read);
+ int (*rw_wait_done)(struct nfi_base *nb, int sectors, bool read);
+ int (*rw_data)(struct nfi_base *nb, u8 *data, u8 *fdm, int sectors,
+ bool read);
+ void (*rw_complete)(struct nfi_base *nb, u8 *data, u8 *fdm, bool read);
+};
+
+static inline struct nfi_base *nfi_to_base(struct nfi *nfi)
+{
+ return container_of(nfi, struct nfi_base, nfi);
+}
+
+struct nfi *nfi_extend_init(struct nfi_base *nb);
+void nfi_extend_exit(struct nfi_base *nb);
+
+#endif /* __NFI_BASE_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/core/nfi/nfi_regs.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NFI_REGS_H__
+#define __NFI_REGS_H__
+
+#define NFI_CNFG 0x000
+#define CNFG_AHB BIT(0)
+#define CNFG_READ_EN BIT(1)
+#define CNFG_DMA_BURST_EN BIT(2)
+#define CNFG_RESEED_SEC_EN BIT(4)
+#define CNFG_RAND_SEL BIT(5)
+#define CNFG_BYTE_RW BIT(6)
+#define CNFG_HW_ECC_EN BIT(8)
+#define CNFG_AUTO_FMT_EN BIT(9)
+#define CNFG_RAND_MASK GENMASK(5, 4)
+#define CNFG_OP_MODE_MASK GENMASK(14, 12)
+#define CNFG_IDLE_MOD 0
+#define CNFG_READ_MODE (1 << 12)
+#define CNFG_SINGLE_READ_MODE (2 << 12)
+#define CNFG_PROGRAM_MODE (3 << 12)
+#define CNFG_ERASE_MODE (4 << 12)
+#define CNFG_RESET_MODE (5 << 12)
+#define CNFG_CUSTOM_MODE (6 << 12)
+#define NFI_PAGEFMT 0x004
+#define PAGEFMT_SPARE_SHIFT 4
+#define PAGEFMT_FDM_ECC_SHIFT 12
+#define PAGEFMT_FDM_SHIFT 8
+#define PAGEFMT_SEC_SEL_512 BIT(2)
+#define PAGEFMT_512_2K 0
+#define PAGEFMT_2K_4K 1
+#define PAGEFMT_4K_8K 2
+#define PAGEFMT_8K_16K 3
+#define NFI_CON 0x008
+#define CON_FIFO_FLUSH BIT(0)
+#define CON_NFI_RST BIT(1)
+#define CON_BRD BIT(8)
+#define CON_BWR BIT(9)
+#define CON_SEC_SHIFT 12
+#define NFI_ACCCON 0x00c
+#define NFI_INTR_EN 0x010
+#define INTR_BUSY_RETURN_EN BIT(4)
+#define INTR_AHB_DONE_EN BIT(6)
+#define NFI_INTR_STA 0x014
+#define NFI_CMD 0x020
+#define NFI_ADDRNOB 0x030
+#define ROW_SHIFT 4
+#define NFI_COLADDR 0x034
+#define NFI_ROWADDR 0x038
+#define NFI_STRDATA 0x040
+#define STAR_EN 1
+#define STAR_DE 0
+#define NFI_CNRNB 0x044
+#define NFI_DATAW 0x050
+#define NFI_DATAR 0x054
+#define NFI_PIO_DIRDY 0x058
+#define PIO_DI_RDY 1
+#define NFI_STA 0x060
+#define STA_CMD BIT(0)
+#define STA_ADDR BIT(1)
+#define FLASH_MACRO_IDLE BIT(5)
+#define STA_BUSY BIT(8)
+#define STA_BUSY2READY BIT(9)
+#define STA_EMP_PAGE BIT(12)
+#define NFI_FSM_CUSTDATA (0xe << 16)
+#define NFI_FSM_MASK GENMASK(19, 16)
+#define NAND_FSM_MASK GENMASK(29, 23)
+#define NFI_ADDRCNTR 0x070
+#define CNTR_VALID_MASK GENMASK(16, 0)
+#define CNTR_MASK GENMASK(15, 12)
+#define ADDRCNTR_SEC_SHIFT 12
+#define ADDRCNTR_SEC(val) \
+ (((val) & CNTR_MASK) >> ADDRCNTR_SEC_SHIFT)
+#define NFI_STRADDR 0x080
+#define NFI_BYTELEN 0x084
+#define NFI_CSEL 0x090
+#define NFI_FDML(x) (0x0a0 + (x) * 8)
+#define NFI_FDMM(x) (0x0a4 + (x) * 8)
+#define NFI_DEBUG_CON1 0x220
+#define STROBE_MASK GENMASK(4, 3)
+#define STROBE_SHIFT 3
+#define ECC_CLK_EN BIT(11)
+#define AUTOC_SRAM_MODE BIT(12)
+#define BYPASS_MASTER_EN BIT(15)
+#define NFI_MASTER_STA 0x224
+#define MASTER_BUS_BUSY 0x3
+#define NFI_SECCUS_SIZE 0x22c
+#define SECCUS_SIZE_EN BIT(17)
+#define NFI_RANDOM_CNFG 0x238
+#define RAN_ENCODE_EN BIT(0)
+#define ENCODE_SEED_SHIFT 1
+#define RAN_DECODE_EN BIT(16)
+#define DECODE_SEED_SHIFT 17
+#define RAN_SEED_MASK 0x7fff
+#define NFI_EMPTY_THRESH 0x23c
+#define NFI_NAND_TYPE_CNFG 0x240
+#define NAND_TYPE_ASYNC 0
+#define NAND_TYPE_TOGGLE 1
+#define NAND_TYPE_SYNC 2
+#define NFI_ACCCON1 0x244
+#define NFI_DELAY_CTRL 0x248
+#define NFI_TLC_RD_WHR2 0x300
+#define TLC_RD_WHR2_EN BIT(12)
+#define TLC_RD_WHR2_MASK GENMASK(11, 0)
+#define SNF_SNF_CNFG 0x55c
+#define SPI_MODE_EN 1
+#define SPI_MODE_DIS 0
+
+#endif /* __NFI_REGS_H__ */
+
--- /dev/null
+++ b/drivers/mtd/nandx/core/nfi/nfi_spi.c
@@ -0,0 +1,689 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#include "nandx_util.h"
+#include "nandx_core.h"
+#include "../nfi.h"
+#include "nfiecc.h"
+#include "nfi_regs.h"
+#include "nfi_base.h"
+#include "nfi_spi_regs.h"
+#include "nfi_spi.h"
+
+#define NFI_CMD_DUMMY_RD 0x00
+#define NFI_CMD_DUMMY_WR 0x80
+
+static struct nfi_spi_delay spi_delay[SPI_NAND_MAX_DELAY] = {
+ /*
+ * tCLK_SAM_DLY, tCLK_OUT_DLY, tCS_DLY, tWR_EN_DLY,
+ * tIO_IN_DLY[4], tIO_OUT_DLY[4], tREAD_LATCH_LATENCY
+ */
+ {0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0}, 0},
+ {21, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0}, 0},
+ {63, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0}, 0},
+ {0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0}, 1},
+ {21, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0}, 1},
+ {63, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0}, 1}
+};
+
+static inline struct nfi_spi *base_to_snfi(struct nfi_base *nb)
+{
+ return container_of(nb, struct nfi_spi, base);
+}
+
+static void snfi_mac_enable(struct nfi_base *nb)
+{
+ void *regs = nb->res.nfi_regs;
+ u32 val;
+
+ val = readl(regs + SNF_MAC_CTL);
+ val &= ~MAC_XIO_SEL;
+ val |= SF_MAC_EN;
+
+ writel(val, regs + SNF_MAC_CTL);
+}
+
+static void snfi_mac_disable(struct nfi_base *nb)
+{
+ void *regs = nb->res.nfi_regs;
+ u32 val;
+
+ val = readl(regs + SNF_MAC_CTL);
+ val &= ~(SF_TRIG | SF_MAC_EN);
+ writel(val, regs + SNF_MAC_CTL);
+}
+
+static int snfi_mac_trigger(struct nfi_base *nb)
+{
+ void *regs = nb->res.nfi_regs;
+ int ret;
+ u32 val;
+
+ val = readl(regs + SNF_MAC_CTL);
+ val |= SF_TRIG;
+ writel(val, regs + SNF_MAC_CTL);
+
+ ret = readl_poll_timeout_atomic(regs + SNF_MAC_CTL, val,
+ val & WIP_READY, 10,
+ NFI_TIMEOUT);
+ if (ret) {
+ pr_info("polling wip ready for read timeout\n");
+ return ret;
+ }
+
+ return readl_poll_timeout_atomic(regs + SNF_MAC_CTL, val,
+ !(val & WIP), 10,
+ NFI_TIMEOUT);
+}
+
+static int snfi_mac_op(struct nfi_base *nb)
+{
+ int ret;
+
+ snfi_mac_enable(nb);
+ ret = snfi_mac_trigger(nb);
+ snfi_mac_disable(nb);
+
+ return ret;
+}
+
+static void snfi_write_mac(struct nfi_spi *nfi_spi, u8 *data, int count)
+{
+ struct nandx_split32 split = {0};
+ u32 reg_offset = round_down(nfi_spi->tx_count, 4);
+ void *regs = nfi_spi->base.res.nfi_regs;
+ u32 data_offset = 0, i, val;
+ u8 *p_val = (u8 *)(&val);
+
+ nandx_split(&split, nfi_spi->tx_count, count, val, 4);
+
+ if (split.head_len) {
+ val = readl(regs + SPI_GPRAM_ADDR + reg_offset);
+
+ for (i = 0; i < split.head_len; i++)
+ p_val[split.head + i] = data[i];
+
+ writel(val, regs + SPI_GPRAM_ADDR + reg_offset);
+ }
+
+ if (split.body_len) {
+ reg_offset = split.body;
+ data_offset = split.head_len;
+
+ for (i = 0; i < split.body_len; i++) {
+ p_val[i & 3] = data[data_offset + i];
+
+ if ((i & 3) == 3) {
+ writel(val, regs + SPI_GPRAM_ADDR + reg_offset);
+ reg_offset += 4;
+ }
+ }
+ }
+
+ if (split.tail_len) {
+ reg_offset = split.tail;
+ data_offset += split.body_len;
+
+ for (i = 0; i < split.tail_len; i++) {
+ p_val[i] = data[data_offset + i];
+
+ if (i == split.tail_len - 1)
+ writel(val, regs + SPI_GPRAM_ADDR + reg_offset);
+ }
+ }
+}
+
+static void snfi_read_mac(struct nfi_spi *nfi_spi, u8 *data, int count)
+{
+ void *regs = nfi_spi->base.res.nfi_regs;
+ u32 reg_offset = round_down(nfi_spi->tx_count, 4);
+ struct nandx_split32 split = {0};
+ u32 data_offset = 0, i, val;
+ u8 *p_val = (u8 *)&val;
+
+ nandx_split(&split, nfi_spi->tx_count, count, val, 4);
+
+ if (split.head_len) {
+ val = readl(regs + SPI_GPRAM_ADDR + reg_offset);
+
+ for (i = 0; i < split.head_len; i++)
+ data[data_offset + i] = p_val[split.head + i];
+ }
+
+ if (split.body_len) {
+ reg_offset = split.body;
+ data_offset = split.head_len;
+
+ for (i = 0; i < split.body_len; i++) {
+ if ((i & 3) == 0) {
+ val = readl(regs + SPI_GPRAM_ADDR + reg_offset);
+ reg_offset += 4;
+ }
+
+ data[data_offset + i] = p_val[i % 4];
+ }
+ }
+
+ if (split.tail_len) {
+ reg_offset = split.tail;
+ data_offset += split.body_len;
+ val = readl(regs + SPI_GPRAM_ADDR + reg_offset);
+
+ for (i = 0; i < split.tail_len; i++)
+ data[data_offset + i] = p_val[i];
+ }
+}
+
+static int snfi_send_command(struct nfi *nfi, short cmd)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ struct nfi_spi *nfi_spi = base_to_snfi(nb);
+
+ if (cmd == -1)
+ return 0;
+
+ if (nfi_spi->snfi_mode == SNFI_MAC_MODE) {
+ snfi_write_mac(nfi_spi, (u8 *)&cmd, 1);
+ nfi_spi->tx_count++;
+ return 0;
+ }
+
+ nfi_spi->cmd[nfi_spi->cur_cmd_idx++] = cmd;
+ return 0;
+}
+
+static int snfi_send_address(struct nfi *nfi, int col, int row,
+ int col_cycle,
+ int row_cycle)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ struct nfi_spi *nfi_spi = base_to_snfi(nb);
+ u32 addr, cycle, temp;
+
+ nb->col = col;
+ nb->row = row;
+
+ if (nfi_spi->snfi_mode == SNFI_MAC_MODE) {
+ addr = row;
+ cycle = row_cycle;
+
+ if (!row_cycle) {
+ addr = col;
+ cycle = col_cycle;
+ }
+
+ temp = nandx_cpu_to_be32(addr) >> ((4 - cycle) << 3);
+ snfi_write_mac(nfi_spi, (u8 *)&temp, cycle);
+ nfi_spi->tx_count += cycle;
+ } else {
+ nfi_spi->row_addr[nfi_spi->cur_addr_idx++] = row;
+ nfi_spi->col_addr[nfi_spi->cur_addr_idx++] = col;
+ }
+
+ return 0;
+}
+
+static int snfi_trigger(struct nfi *nfi)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ struct nfi_spi *nfi_spi = base_to_snfi(nb);
+ void *regs = nb->res.nfi_regs;
+
+ writel(nfi_spi->tx_count, regs + SNF_MAC_OUTL);
+ writel(0, regs + SNF_MAC_INL);
+
+ nfi_spi->tx_count = 0;
+ nfi_spi->cur_cmd_idx = 0;
+ nfi_spi->cur_addr_idx = 0;
+
+ return snfi_mac_op(nb);
+}
+
+static int snfi_select_chip(struct nfi *nfi, int cs)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ void *regs = nb->res.nfi_regs;
+ u32 val;
+
+ val = readl(regs + SNF_MISC_CTL);
+
+ if (cs == 0) {
+ val &= ~SF2CS_SEL;
+ val &= ~SF2CS_EN;
+ } else if (cs == 1) {
+ val |= SF2CS_SEL;
+ val |= SF2CS_EN;
+ } else {
+ return -EIO;
+ }
+
+ writel(val, regs + SNF_MISC_CTL);
+
+ return 0;
+}
+
+static int snfi_set_delay(struct nfi_base *nb, u8 delay_mode)
+{
+ void *regs = nb->res.nfi_regs;
+ struct nfi_spi_delay *delay;
+ u32 val;
+
+ if (delay_mode < 0 || delay_mode > SPI_NAND_MAX_DELAY)
+ return -EINVAL;
+
+ delay = &spi_delay[delay_mode];
+
+ val = delay->tIO_OUT_DLY[0] | delay->tIO_OUT_DLY[1] << 8 |
+ delay->tIO_OUT_DLY[2] << 16 |
+ delay->tIO_OUT_DLY[3] << 24;
+ writel(val, regs + SNF_DLY_CTL1);
+
+ val = delay->tIO_IN_DLY[0] | (delay->tIO_IN_DLY[1] << 8) |
+ delay->tIO_IN_DLY[2] << 16 |
+ delay->tIO_IN_DLY[3] << 24;
+ writel(val, regs + SNF_DLY_CTL2);
+
+ val = delay->tCLK_SAM_DLY | delay->tCLK_OUT_DLY << 8 |
+ delay->tCS_DLY << 16 |
+ delay->tWR_EN_DLY << 24;
+ writel(val, regs + SNF_DLY_CTL3);
+
+ writel(delay->tCS_DLY, regs + SNF_DLY_CTL4);
+
+ val = readl(regs + SNF_MISC_CTL);
+ val |= (delay->tREAD_LATCH_LATENCY) <<
+ LATCH_LAT_SHIFT;
+ writel(val, regs + SNF_MISC_CTL);
+
+ return 0;
+}
+
+static int snfi_set_timing(struct nfi *nfi, void *timing, int type)
+{
+ /* Nothing need to do. */
+ return 0;
+}
+
+static int snfi_wait_ready(struct nfi *nfi, int type, u32 timeout)
+{
+ /* Nothing need to do. */
+ return 0;
+}
+
+static int snfi_ctrl(struct nfi *nfi, int cmd, void *args)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ struct nfi_spi *nfi_spi = base_to_snfi(nb);
+ int ret = 0;
+
+ if (!args)
+ return -EINVAL;
+
+ switch (cmd) {
+ case NFI_CTRL_DMA:
+ nb->dma_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_NFI_IRQ:
+ nb->nfi_irq_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_ECC_IRQ:
+ nb->ecc_irq_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_PAGE_IRQ:
+ nb->page_irq_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_ECC:
+ nb->ecc_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_BAD_MARK_SWAP:
+ nb->bad_mark_swap_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_ECC_CLOCK:
+ nb->ecc_clk_en = *(bool *)args;
+ break;
+
+ case SNFI_CTRL_OP_MODE:
+ nfi_spi->snfi_mode = *(u8 *)args;
+ break;
+
+ case SNFI_CTRL_RX_MODE:
+ nfi_spi->read_cache_mode = *(u8 *)args;
+ break;
+
+ case SNFI_CTRL_TX_MODE:
+ nfi_spi->write_cache_mode = *(u8 *)args;
+ break;
+
+ case SNFI_CTRL_DELAY_MODE:
+ ret = snfi_set_delay(nb, *(u8 *)args);
+ break;
+
+ default:
+ pr_info("operation not support.\n");
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ return ret;
+}
+
+static int snfi_read_bytes(struct nfi *nfi, u8 *data, int count)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ struct nfi_spi *nfi_spi = base_to_snfi(nb);
+ void *regs = nb->res.nfi_regs;
+ int ret;
+
+ writel(nfi_spi->tx_count, regs + SNF_MAC_OUTL);
+ writel(count, regs + SNF_MAC_INL);
+
+ ret = snfi_mac_op(nb);
+ if (ret)
+ return ret;
+
+ snfi_read_mac(nfi_spi, data, count);
+
+ nfi_spi->tx_count = 0;
+
+ return 0;
+}
+
+static int snfi_write_bytes(struct nfi *nfi, u8 *data, int count)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ struct nfi_spi *nfi_spi = base_to_snfi(nb);
+ void *regs = nb->res.nfi_regs;
+
+ snfi_write_mac(nfi_spi, data, count);
+ nfi_spi->tx_count += count;
+
+ writel(0, regs + SNF_MAC_INL);
+ writel(nfi_spi->tx_count, regs + SNF_MAC_OUTL);
+
+ nfi_spi->tx_count = 0;
+
+ return snfi_mac_op(nb);
+}
+
+static int snfi_reset(struct nfi *nfi)
+{
+ struct nfi_base *nb = nfi_to_base(nfi);
+ struct nfi_spi *nfi_spi = base_to_snfi(nb);
+ void *regs = nb->res.nfi_regs;
+ u32 val;
+ int ret;
+
+ ret = nfi_spi->parent->nfi.reset(nfi);
+ if (ret)
+ return ret;
+
+ val = readl(regs + SNF_MISC_CTL);
+ val |= SW_RST;
+ writel(val, regs + SNF_MISC_CTL);
+
+ ret = readx_poll_timeout_atomic(readw, regs + SNF_STA_CTL1, val,
+ !(val & SPI_STATE), 50,
+ NFI_TIMEOUT);
+ if (ret) {
+ pr_info("spi state active in reset [0x%x] = 0x%x\n",
+ SNF_STA_CTL1, val);
+ return ret;
+ }
+
+ val = readl(regs + SNF_MISC_CTL);
+ val &= ~SW_RST;
+ writel(val, regs + SNF_MISC_CTL);
+
+ return 0;
+}
+
+static int snfi_config_for_write(struct nfi_base *nb, int count)
+{
+ struct nfi_spi *nfi_spi = base_to_snfi(nb);
+ void *regs = nb->res.nfi_regs;
+ u32 val;
+
+ nb->set_op_mode(regs, CNFG_CUSTOM_MODE);
+
+ val = readl(regs + SNF_MISC_CTL);
+
+ if (nfi_spi->write_cache_mode == SNFI_TX_114)
+ val |= PG_LOAD_X4_EN;
+
+ if (nfi_spi->snfi_mode == SNFI_CUSTOM_MODE)
+ val |= PG_LOAD_CUSTOM_EN;
+
+ writel(val, regs + SNF_MISC_CTL);
+
+ val = count * (nb->nfi.sector_size + nb->nfi.sector_spare_size);
+ writel(val << PG_LOAD_SHIFT, regs + SNF_MISC_CTL2);
+
+ val = readl(regs + SNF_PG_CTL1);
+
+ if (nfi_spi->snfi_mode == SNFI_CUSTOM_MODE)
+ val |= nfi_spi->cmd[0] << PG_LOAD_CMD_SHIFT;
+ else {
+ val |= nfi_spi->cmd[0] | nfi_spi->cmd[1] << PG_LOAD_CMD_SHIFT |
+ nfi_spi->cmd[2] << PG_EXE_CMD_SHIFT;
+
+ writel(nfi_spi->row_addr[1], regs + SNF_PG_CTL3);
+ writel(nfi_spi->cmd[3] << GF_CMD_SHIFT | nfi_spi->col_addr[2] <<
+ GF_ADDR_SHIFT, regs + SNF_GF_CTL1);
+ }
+
+ writel(val, regs + SNF_PG_CTL1);
+ writel(nfi_spi->col_addr[1], regs + SNF_PG_CTL2);
+
+ writel(NFI_CMD_DUMMY_WR, regs + NFI_CMD);
+
+ return 0;
+}
+
+static int snfi_config_for_read(struct nfi_base *nb, int count)
+{
+ struct nfi_spi *nfi_spi = base_to_snfi(nb);
+ void *regs = nb->res.nfi_regs;
+ u32 val;
+ int ret = 0;
+
+ nb->set_op_mode(regs, CNFG_CUSTOM_MODE);
+
+ val = readl(regs + SNF_MISC_CTL);
+ val &= ~DARA_READ_MODE_MASK;
+
+ switch (nfi_spi->read_cache_mode) {
+
+ case SNFI_RX_111:
+ break;
+
+ case SNFI_RX_112:
+ val |= X2_DATA_MODE << READ_MODE_SHIFT;
+ break;
+
+ case SNFI_RX_114:
+ val |= X4_DATA_MODE << READ_MODE_SHIFT;
+ break;
+
+ case SNFI_RX_122:
+ val |= DUAL_IO_MODE << READ_MODE_SHIFT;
+ break;
+
+ case SNFI_RX_144:
+ val |= QUAD_IO_MODE << READ_MODE_SHIFT;
+ break;
+
+ default:
+ pr_info("Not support this read operarion: %d!\n",
+ nfi_spi->read_cache_mode);
+ ret = -EINVAL;
+ break;
+ }
+
+ if (nfi_spi->snfi_mode == SNFI_CUSTOM_MODE)
+ val |= DATARD_CUSTOM_EN;
+
+ writel(val, regs + SNF_MISC_CTL);
+
+ val = count * (nb->nfi.sector_size + nb->nfi.sector_spare_size);
+ writel(val, regs + SNF_MISC_CTL2);
+
+ val = readl(regs + SNF_RD_CTL2);
+
+ if (nfi_spi->snfi_mode == SNFI_CUSTOM_MODE) {
+ val |= nfi_spi->cmd[0];
+ writel(nfi_spi->col_addr[1], regs + SNF_RD_CTL3);
+ } else {
+ val |= nfi_spi->cmd[2];
+ writel(nfi_spi->cmd[0] << PAGE_READ_CMD_SHIFT |
+ nfi_spi->row_addr[0], regs + SNF_RD_CTL1);
+ writel(nfi_spi->cmd[1] << GF_CMD_SHIFT |
+ nfi_spi->col_addr[1] << GF_ADDR_SHIFT,
+ regs + SNF_GF_CTL1);
+ writel(nfi_spi->col_addr[2], regs + SNF_RD_CTL3);
+ }
+
+ writel(val, regs + SNF_RD_CTL2);
+
+ writel(NFI_CMD_DUMMY_RD, regs + NFI_CMD);
+
+ return ret;
+}
+
+static bool is_page_empty(struct nfi_base *nb, u8 *data, u8 *fdm,
+ int sectors)
+{
+ u32 *data32 = (u32 *)data;
+ u32 *fdm32 = (u32 *)fdm;
+ u32 i, count = 0;
+
+ for (i = 0; i < nb->format.page_size >> 2; i++) {
+ if (data32[i] != 0xffff) {
+ count += zero_popcount(data32[i]);
+ if (count > 10) {
+ pr_info("%s %d %d count:%d\n",
+ __func__, __LINE__, i, count);
+ return false;
+ }
+ }
+ }
+
+ if (fdm) {
+ for (i = 0; i < (nb->nfi.fdm_size * sectors >> 2); i++)
+ if (fdm32[i] != 0xffff) {
+ count += zero_popcount(fdm32[i]);
+ if (count > 10) {
+ pr_info("%s %d %d count:%d\n",
+ __func__, __LINE__, i, count);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static int rw_prepare(struct nfi_base *nb, int sectors, u8 *data,
+ u8 *fdm,
+ bool read)
+{
+ struct nfi_spi *nfi_spi = base_to_snfi(nb);
+ int ret;
+
+ ret = nfi_spi->parent->rw_prepare(nb, sectors, data, fdm, read);
+ if (ret)
+ return ret;
+
+ if (read)
+ ret = snfi_config_for_read(nb, sectors);
+ else
+ ret = snfi_config_for_write(nb, sectors);
+
+ return ret;
+}
+
+static void rw_complete(struct nfi_base *nb, u8 *data, u8 *fdm,
+ bool read)
+{
+ struct nfi_spi *nfi_spi = base_to_snfi(nb);
+ void *regs = nb->res.nfi_regs;
+ u32 val;
+
+ nfi_spi->parent->rw_complete(nb, data, fdm, read);
+
+ val = readl(regs + SNF_MISC_CTL);
+
+ if (read)
+ val &= ~DATARD_CUSTOM_EN;
+ else
+ val &= ~PG_LOAD_CUSTOM_EN;
+
+ writel(val, regs + SNF_MISC_CTL);
+
+ nfi_spi->tx_count = 0;
+ nfi_spi->cur_cmd_idx = 0;
+ nfi_spi->cur_addr_idx = 0;
+}
+
+static void set_nfi_base_funcs(struct nfi_base *nb)
+{
+ nb->nfi.reset = snfi_reset;
+ nb->nfi.set_timing = snfi_set_timing;
+ nb->nfi.wait_ready = snfi_wait_ready;
+
+ nb->nfi.send_cmd = snfi_send_command;
+ nb->nfi.send_addr = snfi_send_address;
+ nb->nfi.trigger = snfi_trigger;
+ nb->nfi.nfi_ctrl = snfi_ctrl;
+ nb->nfi.select_chip = snfi_select_chip;
+
+ nb->nfi.read_bytes = snfi_read_bytes;
+ nb->nfi.write_bytes = snfi_write_bytes;
+
+ nb->rw_prepare = rw_prepare;
+ nb->rw_complete = rw_complete;
+ nb->is_page_empty = is_page_empty;
+
+}
+
+struct nfi *nfi_extend_init(struct nfi_base *nb)
+{
+ struct nfi_spi *nfi_spi;
+
+ nfi_spi = mem_alloc(1, sizeof(struct nfi_spi));
+ if (!nfi_spi) {
+ pr_info("snfi alloc memory fail @%s.\n", __func__);
+ return NULL;
+ }
+
+ memcpy(&nfi_spi->base, nb, sizeof(struct nfi_base));
+ nfi_spi->parent = nb;
+
+ nfi_spi->read_cache_mode = SNFI_RX_114;
+ nfi_spi->write_cache_mode = SNFI_TX_114;
+
+ set_nfi_base_funcs(&nfi_spi->base);
+
+ /* Change nfi to spi mode */
+ writel(SPI_MODE, nb->res.nfi_regs + SNF_SNF_CNFG);
+
+ return &(nfi_spi->base.nfi);
+}
+
+void nfi_extend_exit(struct nfi_base *nb)
+{
+ struct nfi_spi *nfi_spi = base_to_snfi(nb);
+
+ mem_free(nfi_spi->parent);
+ mem_free(nfi_spi);
+}
+
--- /dev/null
+++ b/drivers/mtd/nandx/core/nfi/nfi_spi.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NFI_SPI_H__
+#define __NFI_SPI_H__
+
+#define SPI_NAND_MAX_DELAY 6
+#define SPI_NAND_MAX_OP 4
+
+/*TODO - add comments */
+struct nfi_spi_delay {
+ u8 tCLK_SAM_DLY;
+ u8 tCLK_OUT_DLY;
+ u8 tCS_DLY;
+ u8 tWR_EN_DLY;
+ u8 tIO_IN_DLY[4];
+ u8 tIO_OUT_DLY[4];
+ u8 tREAD_LATCH_LATENCY;
+};
+
+/* SPI Nand structure */
+struct nfi_spi {
+ struct nfi_base base;
+ struct nfi_base *parent;
+
+ u8 snfi_mode;
+ u8 tx_count;
+
+ u8 cmd[SPI_NAND_MAX_OP];
+ u8 cur_cmd_idx;
+
+ u32 row_addr[SPI_NAND_MAX_OP];
+ u32 col_addr[SPI_NAND_MAX_OP];
+ u8 cur_addr_idx;
+
+ u8 read_cache_mode;
+ u8 write_cache_mode;
+};
+
+#endif /* __NFI_SPI_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/core/nfi/nfi_spi_regs.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NFI_SPI_REGS_H__
+#define __NFI_SPI_REGS_H__
+
+#define SNF_MAC_CTL 0x500
+#define WIP BIT(0)
+#define WIP_READY BIT(1)
+#define SF_TRIG BIT(2)
+#define SF_MAC_EN BIT(3)
+#define MAC_XIO_SEL BIT(4)
+#define SNF_MAC_OUTL 0x504
+#define SNF_MAC_INL 0x508
+#define SNF_RD_CTL1 0x50c
+#define PAGE_READ_CMD_SHIFT 24
+#define SNF_RD_CTL2 0x510
+#define SNF_RD_CTL3 0x514
+#define SNF_GF_CTL1 0x518
+#define GF_ADDR_SHIFT 16
+#define GF_CMD_SHIFT 24
+#define SNF_GF_CTL3 0x520
+#define SNF_PG_CTL1 0x524
+#define PG_EXE_CMD_SHIFT 16
+#define PG_LOAD_CMD_SHIFT 8
+#define SNF_PG_CTL2 0x528
+#define SNF_PG_CTL3 0x52c
+#define SNF_ER_CTL 0x530
+#define SNF_ER_CTL2 0x534
+#define SNF_MISC_CTL 0x538
+#define SW_RST BIT(28)
+#define PG_LOAD_X4_EN BIT(20)
+#define X2_DATA_MODE 1
+#define X4_DATA_MODE 2
+#define DUAL_IO_MODE 5
+#define QUAD_IO_MODE 6
+#define READ_MODE_SHIFT 16
+#define LATCH_LAT_SHIFT 8
+#define LATCH_LAT_MASK GENMASK(9, 8)
+#define DARA_READ_MODE_MASK GENMASK(18, 16)
+#define SF2CS_SEL BIT(13)
+#define SF2CS_EN BIT(12)
+#define PG_LOAD_CUSTOM_EN BIT(7)
+#define DATARD_CUSTOM_EN BIT(6)
+#define SNF_MISC_CTL2 0x53c
+#define PG_LOAD_SHIFT 16
+#define SNF_DLY_CTL1 0x540
+#define SNF_DLY_CTL2 0x544
+#define SNF_DLY_CTL3 0x548
+#define SNF_DLY_CTL4 0x54c
+#define SNF_STA_CTL1 0x550
+#define SPI_STATE GENMASK(3, 0)
+#define SNF_STA_CTL2 0x554
+#define SNF_STA_CTL3 0x558
+#define SNF_SNF_CNFG 0x55c
+#define SPI_MODE BIT(0)
+#define SNF_DEBUG_SEL 0x560
+#define SPI_GPRAM_ADDR 0x800
+
+#endif /* __NFI_SPI_REGS_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/core/nfi/nfiecc.c
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#include "nandx_util.h"
+#include "nandx_core.h"
+#include "nfiecc_regs.h"
+#include "nfiecc.h"
+
+#define NFIECC_IDLE_REG(op) \
+ ((op) == ECC_ENCODE ? NFIECC_ENCIDLE : NFIECC_DECIDLE)
+#define IDLE_MASK 1
+#define NFIECC_CTL_REG(op) \
+ ((op) == ECC_ENCODE ? NFIECC_ENCCON : NFIECC_DECCON)
+#define NFIECC_IRQ_REG(op) \
+ ((op) == ECC_ENCODE ? NFIECC_ENCIRQEN : NFIECC_DECIRQEN)
+#define NFIECC_ADDR(op) \
+ ((op) == ECC_ENCODE ? NFIECC_ENCDIADDR : NFIECC_DECDIADDR)
+
+#define ECC_TIMEOUT 500000
+
+/* ecc strength that each IP supports */
+static const int ecc_strength_mt7622[] = {
+ 4, 6, 8, 10, 12, 14, 16
+};
+
+static int nfiecc_irq_handler(void *data)
+{
+ struct nfiecc *ecc = data;
+ void *regs = ecc->res.regs;
+ u32 status;
+
+ status = readl(regs + NFIECC_DECIRQSTA) & DEC_IRQSTA_GEN;
+ if (status) {
+ status = readl(regs + NFIECC_DECDONE);
+ if (!(status & ecc->config.sectors))
+ return NAND_IRQ_NONE;
+
+ /*
+ * Clear decode IRQ status once again to ensure that
+ * there will be no extra IRQ.
+ */
+ readl(regs + NFIECC_DECIRQSTA);
+ ecc->config.sectors = 0;
+ nandx_event_complete(ecc->done);
+ } else {
+ status = readl(regs + NFIECC_ENCIRQSTA) & ENC_IRQSTA_GEN;
+ if (!status)
+ return NAND_IRQ_NONE;
+
+ nandx_event_complete(ecc->done);
+ }
+
+ return NAND_IRQ_HANDLED;
+}
+
+static inline int nfiecc_wait_idle(struct nfiecc *ecc)
+{
+ int op = ecc->config.op;
+ int ret, val;
+
+ ret = readl_poll_timeout_atomic(ecc->res.regs + NFIECC_IDLE_REG(op),
+ val, val & IDLE_MASK,
+ 10, ECC_TIMEOUT);
+ if (ret)
+ pr_info("%s not idle\n",
+ op == ECC_ENCODE ? "encoder" : "decoder");
+
+ return ret;
+}
+
+static int nfiecc_wait_encode_done(struct nfiecc *ecc)
+{
+ int ret, val;
+
+ if (ecc->ecc_irq_en) {
+ /* poll one time to avoid missing irq event */
+ ret = readl_poll_timeout_atomic(ecc->res.regs + NFIECC_ENCSTA,
+ val, val & ENC_FSM_IDLE, 1, 1);
+ if (!ret)
+ return 0;
+
+ /* irq done, if not, we can go on to poll status for a while */
+ ret = nandx_event_wait_complete(ecc->done, ECC_TIMEOUT);
+ if (ret)
+ return 0;
+ }
+
+ ret = readl_poll_timeout_atomic(ecc->res.regs + NFIECC_ENCSTA,
+ val, val & ENC_FSM_IDLE,
+ 10, ECC_TIMEOUT);
+ if (ret)
+ pr_info("encode timeout\n");
+
+ return ret;
+
+}
+
+static int nfiecc_wait_decode_done(struct nfiecc *ecc)
+{
+ u32 secbit = BIT(ecc->config.sectors - 1);
+ void *regs = ecc->res.regs;
+ int ret, val;
+
+ if (ecc->ecc_irq_en) {
+ ret = readl_poll_timeout_atomic(regs + NFIECC_DECDONE,
+ val, val & secbit, 1, 1);
+ if (!ret)
+ return 0;
+
+ ret = nandx_event_wait_complete(ecc->done, ECC_TIMEOUT);
+ if (ret)
+ return 0;
+ }
+
+ ret = readl_poll_timeout_atomic(regs + NFIECC_DECDONE,
+ val, val & secbit,
+ 10, ECC_TIMEOUT);
+ if (ret) {
+ pr_info("decode timeout\n");
+ return ret;
+ }
+
+ /* decode done does not stands for ecc all work done.
+ * we need check syn, bma, chien, autoc all idle.
+ * just check it when ECC_DECCNFG[13:12] is 3,
+ * which means auto correct.
+ */
+ ret = readl_poll_timeout_atomic(regs + NFIECC_DECFSM,
+ val, (val & FSM_MASK) == FSM_IDLE,
+ 10, ECC_TIMEOUT);
+ if (ret)
+ pr_info("decode fsm(0x%x) is not idle\n",
+ readl(regs + NFIECC_DECFSM));
+
+ return ret;
+}
+
+static int nfiecc_wait_done(struct nfiecc *ecc)
+{
+ if (ecc->config.op == ECC_ENCODE)
+ return nfiecc_wait_encode_done(ecc);
+
+ return nfiecc_wait_decode_done(ecc);
+}
+
+static void nfiecc_encode_config(struct nfiecc *ecc, u32 ecc_idx)
+{
+ struct nfiecc_config *config = &ecc->config;
+ u32 val;
+
+ val = ecc_idx | (config->mode << ecc->caps->ecc_mode_shift);
+
+ if (config->mode == ECC_DMA_MODE)
+ val |= ENC_BURST_EN;
+
+ val |= (config->len << 3) << ENCCNFG_MS_SHIFT;
+ writel(val, ecc->res.regs + NFIECC_ENCCNFG);
+}
+
+static void nfiecc_decode_config(struct nfiecc *ecc, u32 ecc_idx)
+{
+ struct nfiecc_config *config = &ecc->config;
+ u32 dec_sz = (config->len << 3) +
+ config->strength * ecc->caps->parity_bits;
+ u32 val;
+
+ val = ecc_idx | (config->mode << ecc->caps->ecc_mode_shift);
+
+ if (config->mode == ECC_DMA_MODE)
+ val |= DEC_BURST_EN;
+
+ val |= (dec_sz << DECCNFG_MS_SHIFT) |
+ (config->deccon << DEC_CON_SHIFT);
+ val |= DEC_EMPTY_EN;
+ writel(val, ecc->res.regs + NFIECC_DECCNFG);
+}
+
+static void nfiecc_config(struct nfiecc *ecc)
+{
+ u32 idx;
+
+ for (idx = 0; idx < ecc->caps->ecc_strength_num; idx++) {
+ if (ecc->config.strength == ecc->caps->ecc_strength[idx])
+ break;
+ }
+
+ if (ecc->config.op == ECC_ENCODE)
+ nfiecc_encode_config(ecc, idx);
+ else
+ nfiecc_decode_config(ecc, idx);
+}
+
+static int nfiecc_enable(struct nfiecc *ecc)
+{
+ enum nfiecc_operation op = ecc->config.op;
+ void *regs = ecc->res.regs;
+
+ nfiecc_config(ecc);
+
+ writel(ECC_OP_EN, regs + NFIECC_CTL_REG(op));
+
+ if (ecc->ecc_irq_en) {
+ writel(ECC_IRQEN, regs + NFIECC_IRQ_REG(op));
+
+ if (ecc->page_irq_en)
+ writel(ECC_IRQEN | ECC_PG_IRQ_SEL,
+ regs + NFIECC_IRQ_REG(op));
+
+ nandx_event_init(ecc->done);
+ }
+
+ return 0;
+}
+
+static int nfiecc_disable(struct nfiecc *ecc)
+{
+ enum nfiecc_operation op = ecc->config.op;
+ void *regs = ecc->res.regs;
+
+ nfiecc_wait_idle(ecc);
+
+ writel(0, regs + NFIECC_IRQ_REG(op));
+ writel(~ECC_OP_EN, regs + NFIECC_CTL_REG(op));
+
+ return 0;
+}
+
+static int nfiecc_correct_data(struct nfiecc *ecc,
+ struct nfiecc_status *status,
+ u8 *data, u32 sector)
+{
+ u32 err, offset, i;
+ u32 loc, byteloc, bitloc;
+
+ status->corrected = 0;
+ status->failed = 0;
+
+ offset = (sector >> 2);
+ err = readl(ecc->res.regs + NFIECC_DECENUM(offset));
+ err >>= (sector % 4) * 8;
+ err &= ecc->caps->err_mask;
+
+ if (err == ecc->caps->err_mask) {
+ status->failed++;
+ return -ENANDREAD;
+ }
+
+ status->corrected += err;
+ status->bitflips = max_t(u32, status->bitflips, err);
+
+ for (i = 0; i < err; i++) {
+ loc = readl(ecc->res.regs + NFIECC_DECEL(i >> 1));
+ loc >>= ((i & 0x1) << 4);
+ byteloc = loc >> 3;
+ bitloc = loc & 0x7;
+ data[byteloc] ^= (1 << bitloc);
+ }
+
+ return 0;
+}
+
+static int nfiecc_fill_data(struct nfiecc *ecc, u8 *data)
+{
+ struct nfiecc_config *config = &ecc->config;
+ void *regs = ecc->res.regs;
+ int size, ret, i;
+ u32 val;
+
+ if (config->mode == ECC_DMA_MODE) {
+ if ((unsigned long)config->dma_addr & 0x3)
+ pr_info("encode address is not 4B aligned: 0x%x\n",
+ (u32)(unsigned long)config->dma_addr);
+
+ writel((unsigned long)config->dma_addr,
+ regs + NFIECC_ADDR(config->op));
+ } else if (config->mode == ECC_PIO_MODE) {
+ if (config->op == ECC_ENCODE) {
+ size = (config->len + 3) >> 2;
+ } else {
+ size = config->strength * ecc->caps->parity_bits;
+ size = (size + 7) >> 3;
+ size += config->len;
+ size >>= 2;
+ }
+
+ for (i = 0; i < size; i++) {
+ ret = readl_poll_timeout_atomic(regs + NFIECC_PIO_DIRDY,
+ val, val & PIO_DI_RDY,
+ 10, ECC_TIMEOUT);
+ if (ret)
+ return ret;
+
+ writel(*((u32 *)data + i), regs + NFIECC_PIO_DI);
+ }
+ }
+
+ return 0;
+}
+
+static int nfiecc_encode(struct nfiecc *ecc, u8 *data)
+{
+ struct nfiecc_config *config = &ecc->config;
+ u32 len, i, val = 0;
+ u8 *p;
+ int ret;
+
+ /* Under NFI mode, nothing need to do */
+ if (config->mode == ECC_NFI_MODE)
+ return 0;
+
+ ret = nfiecc_fill_data(ecc, data);
+ if (ret)
+ return ret;
+
+ ret = nfiecc_wait_encode_done(ecc);
+ if (ret)
+ return ret;
+
+ ret = nfiecc_wait_idle(ecc);
+ if (ret)
+ return ret;
+
+ /* Program ECC bytes to OOB: per sector oob = FDM + ECC + SPARE */
+ len = (config->strength * ecc->caps->parity_bits + 7) >> 3;
+ p = data + config->len;
+
+ /* Write the parity bytes generated by the ECC back to the OOB region */
+ for (i = 0; i < len; i++) {
+ if ((i % 4) == 0)
+ val = readl(ecc->res.regs + NFIECC_ENCPAR(i / 4));
+
+ p[i] = (val >> ((i % 4) * 8)) & 0xff;
+ }
+
+ return 0;
+}
+
+static int nfiecc_decode(struct nfiecc *ecc, u8 *data)
+{
+ int ret;
+
+ /* Under NFI mode, nothing need to do */
+ if (ecc->config.mode == ECC_NFI_MODE)
+ return 0;
+
+ ret = nfiecc_fill_data(ecc, data);
+ if (ret)
+ return ret;
+
+ return nfiecc_wait_decode_done(ecc);
+}
+
+static int nfiecc_decode_status(struct nfiecc *ecc, u32 start_sector,
+ u32 sectors)
+{
+ void *regs = ecc->res.regs;
+ u32 i, val = 0, err;
+ u32 bitflips = 0;
+
+ for (i = start_sector; i < start_sector + sectors; i++) {
+ if ((i % 4) == 0)
+ val = readl(regs + NFIECC_DECENUM(i / 4));
+
+ err = val >> ((i % 4) * 5);
+ err &= ecc->caps->err_mask;
+
+ if (err == ecc->caps->err_mask)
+ pr_err("sector %d is uncorrect\n", i);
+
+ bitflips = max_t(u32, bitflips, err);
+ }
+
+ if (bitflips == ecc->caps->err_mask)
+ return -ENANDREAD;
+
+ if (bitflips)
+ pr_info("bitflips %d is corrected\n", bitflips);
+
+ return bitflips;
+}
+
+static int nfiecc_adjust_strength(struct nfiecc *ecc, int strength)
+{
+ struct nfiecc_caps *caps = ecc->caps;
+ int i, count = caps->ecc_strength_num;
+
+ if (strength >= caps->ecc_strength[count - 1])
+ return caps->ecc_strength[count - 1];
+
+ if (strength < caps->ecc_strength[0])
+ return -EINVAL;
+
+ for (i = 1; i < count; i++) {
+ if (strength < caps->ecc_strength[i])
+ return caps->ecc_strength[i - 1];
+ }
+
+ return -EINVAL;
+}
+
+static int nfiecc_ctrl(struct nfiecc *ecc, int cmd, void *args)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case NFI_CTRL_ECC_IRQ:
+ ecc->ecc_irq_en = *(bool *)args;
+ break;
+
+ case NFI_CTRL_ECC_PAGE_IRQ:
+ ecc->page_irq_en = *(bool *)args;
+ break;
+
+ default:
+ pr_info("invalid arguments.\n");
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int nfiecc_hw_init(struct nfiecc *ecc)
+{
+ int ret;
+
+ ret = nfiecc_wait_idle(ecc);
+ if (ret)
+ return ret;
+
+ writel(~ECC_OP_EN, ecc->res.regs + NFIECC_ENCCON);
+
+ ret = nfiecc_wait_idle(ecc);
+ if (ret)
+ return ret;
+
+ writel(~ECC_OP_EN, ecc->res.regs + NFIECC_DECCON);
+
+ return 0;
+}
+
+static struct nfiecc_caps nfiecc_caps_mt7622 = {
+ .err_mask = 0x1f,
+ .ecc_mode_shift = 4,
+ .parity_bits = 13,
+ .ecc_strength = ecc_strength_mt7622,
+ .ecc_strength_num = 7,
+};
+
+static struct nfiecc_caps *nfiecc_get_match_data(enum mtk_ic_version ic)
+{
+ /* NOTE: add other IC's data */
+ return &nfiecc_caps_mt7622;
+}
+
+struct nfiecc *nfiecc_init(struct nfiecc_resource *res)
+{
+ struct nfiecc *ecc;
+ int ret;
+
+ ecc = mem_alloc(1, sizeof(struct nfiecc));
+ if (!ecc)
+ return NULL;
+
+ ecc->res = *res;
+
+ ret = nandx_irq_register(res->dev, res->irq_id, nfiecc_irq_handler,
+ "mtk-ecc", ecc);
+ if (ret) {
+ pr_info("ecc irq register failed!\n");
+ goto error;
+ }
+
+ ecc->ecc_irq_en = false;
+ ecc->page_irq_en = false;
+ ecc->done = nandx_event_create();
+ ecc->caps = nfiecc_get_match_data(res->ic_ver);
+
+ ecc->adjust_strength = nfiecc_adjust_strength;
+ ecc->enable = nfiecc_enable;
+ ecc->disable = nfiecc_disable;
+ ecc->decode = nfiecc_decode;
+ ecc->encode = nfiecc_encode;
+ ecc->wait_done = nfiecc_wait_done;
+ ecc->decode_status = nfiecc_decode_status;
+ ecc->correct_data = nfiecc_correct_data;
+ ecc->nfiecc_ctrl = nfiecc_ctrl;
+
+ ret = nfiecc_hw_init(ecc);
+ if (ret)
+ return NULL;
+
+ return ecc;
+
+error:
+ mem_free(ecc);
+
+ return NULL;
+}
+
+void nfiecc_exit(struct nfiecc *ecc)
+{
+ nandx_event_destroy(ecc->done);
+ mem_free(ecc);
+}
+
--- /dev/null
+++ b/drivers/mtd/nandx/core/nfi/nfiecc.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NFIECC_H__
+#define __NFIECC_H__
+
+enum nfiecc_mode {
+ ECC_DMA_MODE,
+ ECC_NFI_MODE,
+ ECC_PIO_MODE
+};
+
+enum nfiecc_operation {
+ ECC_ENCODE,
+ ECC_DECODE
+};
+
+enum nfiecc_deccon {
+ ECC_DEC_FER = 1,
+ ECC_DEC_LOCATE = 2,
+ ECC_DEC_CORRECT = 3
+};
+
+struct nfiecc_resource {
+ int ic_ver;
+ void *dev;
+ void *regs;
+ int irq_id;
+
+};
+
+struct nfiecc_status {
+ u32 corrected;
+ u32 failed;
+ u32 bitflips;
+};
+
+struct nfiecc_caps {
+ u32 err_mask;
+ u32 ecc_mode_shift;
+ u32 parity_bits;
+ const int *ecc_strength;
+ u32 ecc_strength_num;
+};
+
+struct nfiecc_config {
+ enum nfiecc_operation op;
+ enum nfiecc_mode mode;
+ enum nfiecc_deccon deccon;
+
+ void *dma_addr; /* DMA use only */
+ u32 strength;
+ u32 sectors;
+ u32 len;
+};
+
+struct nfiecc {
+ struct nfiecc_resource res;
+ struct nfiecc_config config;
+ struct nfiecc_caps *caps;
+
+ bool ecc_irq_en;
+ bool page_irq_en;
+
+ void *done;
+
+ int (*adjust_strength)(struct nfiecc *ecc, int strength);
+ int (*enable)(struct nfiecc *ecc);
+ int (*disable)(struct nfiecc *ecc);
+
+ int (*decode)(struct nfiecc *ecc, u8 *data);
+ int (*encode)(struct nfiecc *ecc, u8 *data);
+
+ int (*decode_status)(struct nfiecc *ecc, u32 start_sector, u32 sectors);
+ int (*correct_data)(struct nfiecc *ecc,
+ struct nfiecc_status *status,
+ u8 *data, u32 sector);
+ int (*wait_done)(struct nfiecc *ecc);
+
+ int (*nfiecc_ctrl)(struct nfiecc *ecc, int cmd, void *args);
+};
+
+struct nfiecc *nfiecc_init(struct nfiecc_resource *res);
+void nfiecc_exit(struct nfiecc *ecc);
+
+#endif /* __NFIECC_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/core/nfi/nfiecc_regs.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NFIECC_REGS_H__
+#define __NFIECC_REGS_H__
+
+#define NFIECC_ENCCON 0x000
+/* NFIECC_DECCON has same bit define */
+#define ECC_OP_EN BIT(0)
+#define NFIECC_ENCCNFG 0x004
+#define ENCCNFG_MS_SHIFT 16
+#define ENC_BURST_EN BIT(8)
+#define NFIECC_ENCDIADDR 0x008
+#define NFIECC_ENCIDLE 0x00c
+#define NFIECC_ENCSTA 0x02c
+#define ENC_FSM_IDLE 1
+#define NFIECC_ENCIRQEN 0x030
+/* NFIECC_DECIRQEN has same bit define */
+#define ECC_IRQEN BIT(0)
+#define ECC_PG_IRQ_SEL BIT(1)
+#define NFIECC_ENCIRQSTA 0x034
+#define ENC_IRQSTA_GEN BIT(0)
+#define NFIECC_PIO_DIRDY 0x080
+#define PIO_DI_RDY BIT(0)
+#define NFIECC_PIO_DI 0x084
+#define NFIECC_DECCON 0x100
+#define NFIECC_DECCNFG 0x104
+#define DEC_BURST_EN BIT(8)
+#define DEC_EMPTY_EN BIT(31)
+#define DEC_CON_SHIFT 12
+#define DECCNFG_MS_SHIFT 16
+#define NFIECC_DECDIADDR 0x108
+#define NFIECC_DECIDLE 0x10c
+#define NFIECC_DECENUM(x) (0x114 + (x) * 4)
+#define NFIECC_DECDONE 0x11c
+#define NFIECC_DECIRQEN 0x140
+#define NFIECC_DECIRQSTA 0x144
+#define DEC_IRQSTA_GEN BIT(0)
+#define NFIECC_DECFSM 0x14c
+#define FSM_MASK 0x7f0f0f0f
+#define FSM_IDLE 0x01010101
+#define NFIECC_BYPASS 0x20c
+#define NFIECC_BYPASS_EN BIT(0)
+#define NFIECC_ENCPAR(x) (0x010 + (x) * 4)
+#define NFIECC_DECEL(x) (0x120 + (x) * 4)
+
+#endif /* __NFIECC_REGS_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/driver/Nandx.mk
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2017 MediaTek Inc.
+# Licensed under either
+# BSD Licence, (see NOTICE for more details)
+# GNU General Public License, version 2.0, (see NOTICE for more details)
+#
+
+nandx-$(NANDX_SIMULATOR_SUPPORT) += simulator/driver.c
+
+nandx-$(NANDX_CTP_SUPPORT) += ctp/ts_nand.c
+nandx-$(NANDX_CTP_SUPPORT) += ctp/nand_test.c
+nandx-header-$(NANDX_CTP_SUPPORT) += ctp/nand_test.h
+
+nandx-$(NANDX_BBT_SUPPORT) += bbt/bbt.c
+nandx-$(NANDX_BROM_SUPPORT) += brom/driver.c
+nandx-$(NANDX_KERNEL_SUPPORT) += kernel/driver.c
+nandx-$(NANDX_LK_SUPPORT) += lk/driver.c
+nandx-$(NANDX_UBOOT_SUPPORT) += uboot/driver.c
--- /dev/null
+++ b/drivers/mtd/nandx/driver/bbt/bbt.c
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#include "nandx_util.h"
+#include "nandx_core.h"
+#include "bbt.h"
+
+/* Not support: multi-chip */
+static u8 main_bbt_pattern[] = {'B', 'b', 't', '0' };
+static u8 mirror_bbt_pattern[] = {'1', 't', 'b', 'B' };
+
+static struct bbt_manager g_bbt_manager = {
+ { {{main_bbt_pattern, 4}, 0, BBT_INVALID_ADDR},
+ {{mirror_bbt_pattern, 4}, 0, BBT_INVALID_ADDR}
+ },
+ NAND_BBT_SCAN_MAXBLOCKS, NULL
+};
+
+static inline void set_bbt_mark(u8 *bbt, int block, u8 mark)
+{
+ int index, offset;
+
+ index = GET_ENTRY(block);
+ offset = GET_POSITION(block);
+
+ bbt[index] &= ~(BBT_ENTRY_MASK << offset);
+ bbt[index] |= (mark & BBT_ENTRY_MASK) << offset;
+ pr_info("%s %d block:%d, bbt[%d]:0x%x, offset:%d, mark:%d\n",
+ __func__, __LINE__, block, index, bbt[index], offset, mark);
+}
+
+static inline u8 get_bbt_mark(u8 *bbt, int block)
+{
+ int offset = GET_POSITION(block);
+ int index = GET_ENTRY(block);
+ u8 value = bbt[index];
+
+ return (value >> offset) & BBT_ENTRY_MASK;
+}
+
+static void mark_nand_bad(struct nandx_info *nand, int block)
+{
+ u8 *buf;
+
+ buf = mem_alloc(1, nand->page_size + nand->oob_size);
+ if (!buf) {
+ pr_info("%s, %d, memory alloc fail, pagesize:%d, oobsize:%d\n",
+ __func__, __LINE__, nand->page_size, nand->oob_size);
+ return;
+ }
+ memset(buf, 0, nand->page_size + nand->oob_size);
+ nandx_erase(block * nand->block_size, nand->block_size);
+ nandx_write(buf, buf + nand->page_size, block * nand->block_size,
+ nand->page_size);
+ mem_free(buf);
+}
+
+static inline bool is_bbt_data(u8 *buf, struct bbt_pattern *pattern)
+{
+ int i;
+
+ for (i = 0; i < pattern->len; i++) {
+ if (buf[i] != pattern->data[i])
+ return false;
+ }
+
+ return true;
+}
+
+static u64 get_bbt_address(struct nandx_info *nand, u8 *bbt,
+ u64 mirror_addr,
+ int max_blocks)
+{
+ u64 addr, end_addr;
+ u8 mark;
+
+ addr = nand->total_size;
+ end_addr = nand->total_size - nand->block_size * max_blocks;
+
+ while (addr > end_addr) {
+ addr -= nand->block_size;
+ mark = get_bbt_mark(bbt, div_down(addr, nand->block_size));
+
+ if (mark == BBT_BLOCK_WORN || mark == BBT_BLOCK_FACTORY_BAD)
+ continue;
+ if (addr != mirror_addr)
+ return addr;
+ }
+
+ return BBT_INVALID_ADDR;
+}
+
+static int read_bbt(struct bbt_desc *desc, u8 *bbt, u32 len)
+{
+ int ret;
+
+ ret = nandx_read(bbt, NULL, desc->bbt_addr + desc->pattern.len + 1,
+ len);
+ if (ret < 0)
+ pr_info("nand_bbt: error reading BBT page, ret:-%x\n", ret);
+
+ return ret;
+}
+
+static void create_bbt(struct nandx_info *nand, u8 *bbt)
+{
+ u32 offset = 0, block = 0;
+
+ do {
+ if (nandx_is_bad_block(offset)) {
+ pr_info("Create bbt at bad block:%d\n", block);
+ set_bbt_mark(bbt, block, BBT_BLOCK_FACTORY_BAD);
+ }
+ block++;
+ offset += nand->block_size;
+ } while (offset < nand->total_size);
+}
+
+static int search_bbt(struct nandx_info *nand, struct bbt_desc *desc,
+ int max_blocks)
+{
+ u64 addr, end_addr;
+ u8 *buf;
+ int ret;
+
+ buf = mem_alloc(1, nand->page_size);
+ if (!buf) {
+ pr_info("%s, %d, mem alloc fail!!! len:%d\n",
+ __func__, __LINE__, nand->page_size);
+ return -ENOMEM;
+ }
+
+ addr = nand->total_size;
+ end_addr = nand->total_size - max_blocks * nand->block_size;
+ while (addr > end_addr) {
+ addr -= nand->block_size;
+
+ nandx_read(buf, NULL, addr, nand->page_size);
+
+ if (is_bbt_data(buf, &desc->pattern)) {
+ desc->bbt_addr = addr;
+ desc->version = buf[desc->pattern.len];
+ pr_info("BBT is found at addr 0x%llx, version %d\n",
+ desc->bbt_addr, desc->version);
+ ret = 0;
+ break;
+ }
+ ret = -EFAULT;
+ }
+
+ mem_free(buf);
+ return ret;
+}
+
+static int save_bbt(struct nandx_info *nand, struct bbt_desc *desc,
+ u8 *bbt)
+{
+ u32 page_size_mask, total_block;
+ int write_len;
+ u8 *buf;
+ int ret;
+
+ ret = nandx_erase(desc->bbt_addr, nand->block_size);
+ if (ret) {
+ pr_info("erase addr 0x%llx fail !!!, ret %d\n",
+ desc->bbt_addr, ret);
+ return ret;
+ }
+
+ total_block = div_down(nand->total_size, nand->block_size);
+ write_len = GET_BBT_LENGTH(total_block) + desc->pattern.len + 1;
+ page_size_mask = nand->page_size - 1;
+ write_len = (write_len + page_size_mask) & (~page_size_mask);
+
+ buf = (u8 *)mem_alloc(1, write_len);
+ if (!buf) {
+ pr_info("%s, %d, mem alloc fail!!! len:%d\n",
+ __func__, __LINE__, write_len);
+ return -ENOMEM;
+ }
+ memset(buf, 0xFF, write_len);
+
+ memcpy(buf, desc->pattern.data, desc->pattern.len);
+ buf[desc->pattern.len] = desc->version;
+
+ memcpy(buf + desc->pattern.len + 1, bbt, GET_BBT_LENGTH(total_block));
+
+ ret = nandx_write(buf, NULL, desc->bbt_addr, write_len);
+
+ if (ret)
+ pr_info("nandx_write fail(%d), offset:0x%llx, len(%d)\n",
+ ret, desc->bbt_addr, write_len);
+ mem_free(buf);
+
+ return ret;
+}
+
+static int write_bbt(struct nandx_info *nand, struct bbt_desc *main,
+ struct bbt_desc *mirror, u8 *bbt, int max_blocks)
+{
+ int block;
+ int ret;
+
+ do {
+ if (main->bbt_addr == BBT_INVALID_ADDR) {
+ main->bbt_addr = get_bbt_address(nand, bbt,
+ mirror->bbt_addr, max_blocks);
+ if (main->bbt_addr == BBT_INVALID_ADDR)
+ return -ENOSPC;
+ }
+
+ ret = save_bbt(nand, main, bbt);
+ if (!ret)
+ break;
+
+ block = div_down(main->bbt_addr, nand->block_size);
+ set_bbt_mark(bbt, block, BBT_BLOCK_WORN);
+ main->version++;
+ mark_nand_bad(nand, block);
+ main->bbt_addr = BBT_INVALID_ADDR;
+ } while (1);
+
+ return 0;
+}
+
+static void mark_bbt_region(struct nandx_info *nand, u8 *bbt, int bbt_blocks)
+{
+ int total_block;
+ int block;
+ u8 mark;
+
+ total_block = div_down(nand->total_size, nand->block_size);
+ block = total_block - bbt_blocks;
+
+ while (bbt_blocks) {
+ mark = get_bbt_mark(bbt, block);
+ if (mark == BBT_BLOCK_GOOD)
+ set_bbt_mark(bbt, block, BBT_BLOCK_RESERVED);
+ block++;
+ bbt_blocks--;
+ }
+}
+
+static void unmark_bbt_region(struct nandx_info *nand, u8 *bbt, int bbt_blocks)
+{
+ int total_block;
+ int block;
+ u8 mark;
+
+ total_block = div_down(nand->total_size, nand->block_size);
+ block = total_block - bbt_blocks;
+
+ while (bbt_blocks) {
+ mark = get_bbt_mark(bbt, block);
+ if (mark == BBT_BLOCK_RESERVED)
+ set_bbt_mark(bbt, block, BBT_BLOCK_GOOD);
+ block++;
+ bbt_blocks--;
+ }
+}
+
+static int update_bbt(struct nandx_info *nand, struct bbt_desc *desc,
+ u8 *bbt,
+ int max_blocks)
+{
+ int ret = 0, i;
+
+ /* The reserved info is not stored in NAND*/
+ unmark_bbt_region(nand, bbt, max_blocks);
+
+ desc[0].version++;
+ for (i = 0; i < 2; i++) {
+ if (i > 0)
+ desc[i].version = desc[i - 1].version;
+
+ ret = write_bbt(nand, &desc[i], &desc[1 - i], bbt, max_blocks);
+ if (ret)
+ break;
+ }
+ mark_bbt_region(nand, bbt, max_blocks);
+
+ return ret;
+}
+
+int scan_bbt(struct nandx_info *nand)
+{
+ struct bbt_manager *manager = &g_bbt_manager;
+ struct bbt_desc *pdesc;
+ int total_block, len, i;
+ int valid_desc = 0;
+ int ret = 0;
+ u8 *bbt;
+
+ total_block = div_down(nand->total_size, nand->block_size);
+ len = GET_BBT_LENGTH(total_block);
+
+ if (!manager->bbt) {
+ manager->bbt = (u8 *)mem_alloc(1, len);
+ if (!manager->bbt) {
+ pr_info("%s, %d, mem alloc fail!!! len:%d\n",
+ __func__, __LINE__, len);
+ return -ENOMEM;
+ }
+ }
+ bbt = manager->bbt;
+ memset(bbt, 0xFF, len);
+
+ /* scan bbt */
+ for (i = 0; i < 2; i++) {
+ pdesc = &manager->desc[i];
+ pdesc->bbt_addr = BBT_INVALID_ADDR;
+ pdesc->version = 0;
+ ret = search_bbt(nand, pdesc, manager->max_blocks);
+ if (!ret && (pdesc->bbt_addr != BBT_INVALID_ADDR))
+ valid_desc += 1 << i;
+ }
+
+ pdesc = &manager->desc[0];
+ if ((valid_desc == 0x3) && (pdesc[0].version != pdesc[1].version))
+ valid_desc = (pdesc[0].version > pdesc[1].version) ? 1 : 2;
+
+ /* read bbt */
+ for (i = 0; i < 2; i++) {
+ if (!(valid_desc & (1 << i)))
+ continue;
+ ret = read_bbt(&pdesc[i], bbt, len);
+ if (ret) {
+ pdesc->bbt_addr = BBT_INVALID_ADDR;
+ pdesc->version = 0;
+ valid_desc &= ~(1 << i);
+ }
+ /* If two BBT version is same, only need to read the first bbt*/
+ if ((valid_desc == 0x3) &&
+ (pdesc[0].version == pdesc[1].version))
+ break;
+ }
+
+ if (!valid_desc) {
+ create_bbt(nand, bbt);
+ pdesc[0].version = 1;
+ pdesc[1].version = 1;
+ }
+
+ pdesc[0].version = max_t(u8, pdesc[0].version, pdesc[1].version);
+ pdesc[1].version = pdesc[0].version;
+
+ for (i = 0; i < 2; i++) {
+ if (valid_desc & (1 << i))
+ continue;
+
+ ret = write_bbt(nand, &pdesc[i], &pdesc[1 - i], bbt,
+ manager->max_blocks);
+ if (ret) {
+ pr_info("write bbt(%d) fail, ret:%d\n", i, ret);
+ manager->bbt = NULL;
+ return ret;
+ }
+ }
+
+ /* Prevent the bbt regions from erasing / writing */
+ mark_bbt_region(nand, manager->bbt, manager->max_blocks);
+
+ for (i = 0; i < total_block; i++) {
+ if (get_bbt_mark(manager->bbt, i) == BBT_BLOCK_WORN)
+ pr_info("Checked WORN bad blk: %d\n", i);
+ else if (get_bbt_mark(manager->bbt, i) == BBT_BLOCK_FACTORY_BAD)
+ pr_info("Checked Factory bad blk: %d\n", i);
+ else if (get_bbt_mark(manager->bbt, i) == BBT_BLOCK_RESERVED)
+ pr_info("Checked Reserved blk: %d\n", i);
+ else if (get_bbt_mark(manager->bbt, i) != BBT_BLOCK_GOOD)
+ pr_info("Checked unknown blk: %d\n", i);
+ }
+
+ return 0;
+}
+
+int bbt_mark_bad(struct nandx_info *nand, off_t offset)
+{
+ struct bbt_manager *manager = &g_bbt_manager;
+ int block = div_down(offset, nand->block_size);
+ int ret = 0;
+
+ mark_nand_bad(nand, block);
+
+#if 0
+ set_bbt_mark(manager->bbt, block, BBT_BLOCK_WORN);
+
+ /* Update flash-based bad block table */
+ ret = update_bbt(nand, manager->desc, manager->bbt,
+ manager->max_blocks);
+#endif
+ pr_info("block %d, update result %d.\n", block, ret);
+
+ return ret;
+}
+
+int bbt_is_bad(struct nandx_info *nand, off_t offset)
+{
+ int block;
+
+ block = div_down(offset, nand->block_size);
+
+ return get_bbt_mark(g_bbt_manager.bbt, block) != BBT_BLOCK_GOOD;
+}
--- /dev/null
+++ b/drivers/mtd/nandx/driver/uboot/driver.c
@@ -0,0 +1,574 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#include <common.h>
+#include <linux/io.h>
+#include <dm.h>
+#include <clk.h>
+#include <nand.h>
+#include <linux/iopoll.h>
+#include <linux/delay.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include "nandx_core.h"
+#include "nandx_util.h"
+#include "bbt.h"
+
+typedef int (*func_nandx_operation)(u8 *, u8 *, u64, size_t);
+
+struct nandx_clk {
+ struct clk *nfi_clk;
+ struct clk *ecc_clk;
+ struct clk *snfi_clk;
+ struct clk *snfi_clk_sel;
+ struct clk *snfi_parent_50m;
+};
+
+struct nandx_nfc {
+ struct nandx_info info;
+ struct nandx_clk clk;
+ struct nfi_resource *res;
+
+ struct nand_chip *nand;
+ spinlock_t lock;
+};
+
+/* Default flash layout for MTK nand controller
+ * 64Bytes oob format.
+ */
+static struct nand_ecclayout eccoob = {
+ .eccbytes = 42,
+ .eccpos = {
+ 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 36, 37, 38, 39, 40, 41
+ },
+ .oobavail = 16,
+ .oobfree = {
+ {
+ .offset = 0,
+ .length = 16,
+ },
+ }
+};
+
+static struct nandx_nfc *mtd_to_nfc(struct mtd_info *mtd)
+{
+ struct nand_chip *nand = mtd_to_nand(mtd);
+
+ return (struct nandx_nfc *)nand_get_controller_data(nand);
+}
+
+static int nandx_enable_clk(struct nandx_clk *clk)
+{
+ int ret;
+
+ ret = clk_enable(clk->nfi_clk);
+ if (ret) {
+ pr_info("failed to enable nfi clk\n");
+ return ret;
+ }
+
+ ret = clk_enable(clk->ecc_clk);
+ if (ret) {
+ pr_info("failed to enable ecc clk\n");
+ goto disable_nfi_clk;
+ }
+
+ ret = clk_enable(clk->snfi_clk);
+ if (ret) {
+ pr_info("failed to enable snfi clk\n");
+ goto disable_ecc_clk;
+ }
+
+ ret = clk_enable(clk->snfi_clk_sel);
+ if (ret) {
+ pr_info("failed to enable snfi clk sel\n");
+ goto disable_snfi_clk;
+ }
+
+ ret = clk_set_parent(clk->snfi_clk_sel, clk->snfi_parent_50m);
+ if (ret) {
+ pr_info("failed to set snfi parent 50MHz\n");
+ goto disable_snfi_clk;
+ }
+
+ return 0;
+
+disable_snfi_clk:
+ clk_disable(clk->snfi_clk);
+disable_ecc_clk:
+ clk_disable(clk->ecc_clk);
+disable_nfi_clk:
+ clk_disable(clk->nfi_clk);
+
+ return ret;
+}
+
+static void nandx_disable_clk(struct nandx_clk *clk)
+{
+ clk_disable(clk->ecc_clk);
+ clk_disable(clk->nfi_clk);
+ clk_disable(clk->snfi_clk);
+}
+
+static int mtk_nfc_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oob_region)
+{
+ struct nandx_nfc *nfc = (struct nandx_nfc *)mtd_to_nfc(mtd);
+ u32 eccsteps;
+
+ eccsteps = div_down(mtd->writesize, mtd->ecc_step_size);
+
+ if (section >= eccsteps)
+ return -EINVAL;
+
+ oob_region->length = nfc->info.fdm_reg_size - nfc->info.fdm_ecc_size;
+ oob_region->offset = section * nfc->info.fdm_reg_size
+ + nfc->info.fdm_ecc_size;
+
+ return 0;
+}
+
+static int mtk_nfc_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oob_region)
+{
+ struct nandx_nfc *nfc = (struct nandx_nfc *)mtd_to_nfc(mtd);
+ u32 eccsteps;
+
+ if (section)
+ return -EINVAL;
+
+ eccsteps = div_down(mtd->writesize, mtd->ecc_step_size);
+ oob_region->offset = nfc->info.fdm_reg_size * eccsteps;
+ oob_region->length = mtd->oobsize - oob_region->offset;
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops mtk_nfc_ooblayout_ops = {
+ .rfree = mtk_nfc_ooblayout_free,
+ .ecc = mtk_nfc_ooblayout_ecc,
+};
+
+struct nfc_compatible {
+ enum mtk_ic_version ic_ver;
+
+ u32 clock_1x;
+ u32 *clock_2x;
+ int clock_2x_num;
+
+ int min_oob_req;
+};
+
+static const struct nfc_compatible nfc_compats_mt7622 = {
+ .ic_ver = NANDX_MT7622,
+ .clock_1x = 26000000,
+ .clock_2x = NULL,
+ .clock_2x_num = 8,
+ .min_oob_req = 1,
+};
+
+static const struct udevice_id ic_of_match[] = {
+ {.compatible = "mediatek,mt7622-nfc", .data = &nfc_compats_mt7622},
+ {}
+};
+
+static int nand_operation(struct mtd_info *mtd, loff_t addr, size_t len,
+ size_t *retlen, uint8_t *data, uint8_t *oob, bool read)
+{
+ struct nandx_split64 split = {0};
+ func_nandx_operation operation;
+ u64 block_oobs, val, align;
+ uint8_t *databuf, *oobbuf;
+ struct nandx_nfc *nfc;
+ bool readoob;
+ int ret = 0;
+
+ nfc = (struct nandx_nfc *)nand_get_controller_data;
+ spin_lock(&nfc->lock);
+
+ databuf = data;
+ oobbuf = oob;
+
+ readoob = data ? false : true;
+ block_oobs = div_up(mtd->erasesize, mtd->writesize) * mtd->oobavail;
+ align = readoob ? block_oobs : mtd->erasesize;
+
+ operation = read ? nandx_read : nandx_write;
+
+ nandx_split(&split, addr, len, val, align);
+
+ if (split.head_len) {
+ ret = operation((u8 *) databuf, oobbuf, addr, split.head_len);
+
+ if (databuf)
+ databuf += split.head_len;
+
+ if (oobbuf)
+ oobbuf += split.head_len;
+
+ addr += split.head_len;
+ *retlen += split.head_len;
+ }
+
+ if (split.body_len) {
+ while (div_up(split.body_len, align)) {
+ ret = operation((u8 *) databuf, oobbuf, addr, align);
+
+ if (databuf) {
+ databuf += mtd->erasesize;
+ split.body_len -= mtd->erasesize;
+ *retlen += mtd->erasesize;
+ }
+
+ if (oobbuf) {
+ oobbuf += block_oobs;
+ split.body_len -= block_oobs;
+ *retlen += block_oobs;
+ }
+
+ addr += mtd->erasesize;
+ }
+
+ }
+
+ if (split.tail_len) {
+ ret = operation((u8 *) databuf, oobbuf, addr, split.tail_len);
+ *retlen += split.tail_len;
+ }
+
+ spin_unlock(&nfc->lock);
+
+ return ret;
+}
+
+static int mtk_nand_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ return nand_operation(mtd, from, len, retlen, buf, NULL, true);
+}
+
+static int mtk_nand_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ return nand_operation(mtd, to, len, retlen, (uint8_t *)buf,
+ NULL, false);
+}
+
+int mtk_nand_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
+{
+ size_t retlen;
+
+ return nand_operation(mtd, from, ops->ooblen, &retlen, NULL,
+ ops->oobbuf, true);
+}
+
+int mtk_nand_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops)
+{
+ size_t retlen;
+
+ return nand_operation(mtd, to, ops->ooblen, &retlen, NULL,
+ ops->oobbuf, false);
+}
+
+static int mtk_nand_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct nandx_nfc *nfc;
+ u64 erase_len, erase_addr;
+ u32 block_size;
+ int ret = 0;
+
+ nfc = (struct nandx_nfc *)mtd_to_nfc(mtd);
+ block_size = nfc->info.block_size;
+ erase_len = instr->len;
+ erase_addr = instr->addr;
+ spin_lock(&nfc->lock);
+ instr->state = MTD_ERASING;
+
+ while (erase_len) {
+ if (mtk_nand_is_bad(mtd, erase_addr)) {
+ pr_info("block(0x%llx) is bad, not erase\n",
+ erase_addr);
+ instr->state = MTD_ERASE_FAILED;
+ goto erase_exit;
+ } else {
+ ret = nandx_erase(erase_addr, block_size);
+ if (ret < 0) {
+ instr->state = MTD_ERASE_FAILED;
+ goto erase_exit;
+ pr_info("erase fail at blk %llu, ret:%d\n",
+ erase_addr, ret);
+ }
+ }
+ erase_addr += block_size;
+ erase_len -= block_size;
+ }
+
+ instr->state = MTD_ERASE_DONE;
+
+erase_exit:
+ ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO;
+
+ spin_unlock(&nfc->lock);
+ /* Do mtd call back function */
+ if (!ret)
+ mtd_erase_callback(instr);
+
+ return ret;
+}
+
+int mtk_nand_is_bad(struct mtd_info *mtd, loff_t ofs)
+{
+ struct nandx_nfc *nfc;
+ int ret;
+
+ nfc = (struct nandx_nfc *)mtd_to_nfc(mtd);
+ spin_lock(&nfc->lock);
+
+ /*ret = bbt_is_bad(&nfc->info, ofs);*/
+ ret = nandx_is_bad_block(ofs);
+ spin_unlock(&nfc->lock);
+
+ if (ret) {
+ pr_info("nand block 0x%x is bad, ret %d!\n", ofs, ret);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+int mtk_nand_mark_bad(struct mtd_info *mtd, loff_t ofs)
+{
+ struct nandx_nfc *nfc;
+ int ret;
+
+ nfc = (struct nandx_nfc *)mtd_to_nfc(mtd);
+ spin_lock(&nfc->lock);
+ pr_info("%s, %d\n", __func__, __LINE__);
+ ret = bbt_mark_bad(&nfc->info, ofs);
+
+ spin_unlock(&nfc->lock);
+
+ return ret;
+}
+
+void mtk_nand_sync(struct mtd_info *mtd)
+{
+ nandx_sync();
+}
+
+static struct mtd_info *mtd_info_create(struct udevice *pdev,
+ struct nandx_nfc *nfc, struct nand_chip *nand)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ int ret;
+
+ nand_set_controller_data(nand, nfc);
+
+ nand->flash_node = dev_of_offset(pdev);
+ nand->ecc.layout = &eccoob;
+
+ ret = nandx_ioctl(CORE_CTRL_NAND_INFO, &nfc->info);
+ if (ret) {
+ pr_info("fail to get nand info (%d)!\n", ret);
+ mem_free(mtd);
+ return NULL;
+ }
+
+ mtd->owner = THIS_MODULE;
+
+ mtd->name = "MTK-SNand";
+ mtd->writesize = nfc->info.page_size;
+ mtd->erasesize = nfc->info.block_size;
+ mtd->oobsize = nfc->info.oob_size;
+ mtd->size = nfc->info.total_size;
+ mtd->type = MTD_NANDFLASH;
+ mtd->flags = MTD_CAP_NANDFLASH;
+ mtd->_erase = mtk_nand_erase;
+ mtd->_read = mtk_nand_read;
+ mtd->_write = mtk_nand_write;
+ mtd->_read_oob = mtk_nand_read_oob;
+ mtd->_write_oob = mtk_nand_write_oob;
+ mtd->_sync = mtk_nand_sync;
+ mtd->_lock = NULL;
+ mtd->_unlock = NULL;
+ mtd->_block_isbad = mtk_nand_is_bad;
+ mtd->_block_markbad = mtk_nand_mark_bad;
+ mtd->writebufsize = mtd->writesize;
+
+ mtd_set_ooblayout(mtd, &mtk_nfc_ooblayout_ops);
+
+ mtd->ecc_strength = nfc->info.ecc_strength;
+ mtd->ecc_step_size = nfc->info.sector_size;
+
+ if (!mtd->bitflip_threshold)
+ mtd->bitflip_threshold = mtd->ecc_strength;
+
+ return mtd;
+}
+
+int board_nand_init(struct nand_chip *nand)
+{
+ struct udevice *dev;
+ struct mtd_info *mtd;
+ struct nandx_nfc *nfc;
+ int arg = 1;
+ int ret;
+
+ ret = uclass_get_device_by_driver(UCLASS_MTD,
+ DM_GET_DRIVER(mtk_snand_drv),
+ &dev);
+ if (ret) {
+ pr_err("Failed to get mtk_nand_drv. (error %d)\n", ret);
+ return ret;
+ }
+
+ nfc = dev_get_priv(dev);
+
+ ret = nandx_enable_clk(&nfc->clk);
+ if (ret) {
+ pr_err("failed to enable nfi clk (error %d)\n", ret);
+ return ret;
+ }
+
+ ret = nandx_init(nfc->res);
+ if (ret) {
+ pr_err("nandx init error (%d)!\n", ret);
+ goto disable_clk;
+ }
+
+ arg = 1;
+ nandx_ioctl(NFI_CTRL_DMA, &arg);
+ nandx_ioctl(NFI_CTRL_ECC, &arg);
+
+#ifdef NANDX_UNIT_TEST
+ nandx_unit_test(0x780000, 0x800);
+#endif
+
+ mtd = mtd_info_create(dev, nfc, nand);
+ if (!mtd) {
+ ret = -ENOMEM;
+ goto disable_clk;
+ }
+
+ spin_lock_init(&nfc->lock);
+#if 0
+ ret = scan_bbt(&nfc->info);
+ if (ret) {
+ pr_info("bbt init error (%d)!\n", ret);
+ goto disable_clk;
+ }
+#endif
+ return ret;
+
+disable_clk:
+ nandx_disable_clk(&nfc->clk);
+
+ return ret;
+}
+
+static int mtk_snand_ofdata_to_platdata(struct udevice *dev)
+{
+ struct nandx_nfc *nfc = dev_get_priv(dev);
+ struct nfc_compatible *compat;
+ struct nfi_resource *res;
+
+ int ret = 0;
+
+ res = mem_alloc(1, sizeof(struct nfi_resource));
+ if (!res)
+ return -ENOMEM;
+
+ nfc->res = res;
+
+ res->nfi_regs = (void *)dev_read_addr_index(dev, 0);
+ res->ecc_regs = (void *)dev_read_addr_index(dev, 1);
+ pr_debug("mtk snand nfi_regs:0x%x ecc_regs:0x%x\n",
+ res->nfi_regs, res->ecc_regs);
+
+ compat = (struct nfc_compatible *)dev_get_driver_data(dev);
+
+ res->ic_ver = (enum mtk_ic_version)(compat->ic_ver);
+ res->clock_1x = compat->clock_1x;
+ res->clock_2x = compat->clock_2x;
+ res->clock_2x_num = compat->clock_2x_num;
+
+ memset(&nfc->clk, 0, sizeof(struct nandx_clk));
+ nfc->clk.nfi_clk =
+ kmalloc(sizeof(*nfc->clk.nfi_clk), GFP_KERNEL);
+ nfc->clk.ecc_clk =
+ kmalloc(sizeof(*nfc->clk.ecc_clk), GFP_KERNEL);
+ nfc->clk.snfi_clk=
+ kmalloc(sizeof(*nfc->clk.snfi_clk), GFP_KERNEL);
+ nfc->clk.snfi_clk_sel =
+ kmalloc(sizeof(*nfc->clk.snfi_clk_sel), GFP_KERNEL);
+ nfc->clk.snfi_parent_50m =
+ kmalloc(sizeof(*nfc->clk.snfi_parent_50m), GFP_KERNEL);
+
+ if (!nfc->clk.nfi_clk || !nfc->clk.ecc_clk || !nfc->clk.snfi_clk ||
+ !nfc->clk.snfi_clk_sel || !nfc->clk.snfi_parent_50m) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ ret = clk_get_by_name(dev, "nfi_clk", nfc->clk.nfi_clk);
+ if (IS_ERR(nfc->clk.nfi_clk)) {
+ ret = PTR_ERR(nfc->clk.nfi_clk);
+ goto err;
+ }
+
+ ret = clk_get_by_name(dev, "ecc_clk", nfc->clk.ecc_clk);
+ if (IS_ERR(nfc->clk.ecc_clk)) {
+ ret = PTR_ERR(nfc->clk.ecc_clk);
+ goto err;
+ }
+
+ ret = clk_get_by_name(dev, "snfi_clk", nfc->clk.snfi_clk);
+ if (IS_ERR(nfc->clk.snfi_clk)) {
+ ret = PTR_ERR(nfc->clk.snfi_clk);
+ goto err;
+ }
+
+ ret = clk_get_by_name(dev, "spinfi_sel", nfc->clk.snfi_clk_sel);
+ if (IS_ERR(nfc->clk.snfi_clk_sel)) {
+ ret = PTR_ERR(nfc->clk.snfi_clk_sel);
+ goto err;
+ }
+
+ ret = clk_get_by_name(dev, "spinfi_parent_50m", nfc->clk.snfi_parent_50m);
+ if (IS_ERR(nfc->clk.snfi_parent_50m))
+ pr_info("spinfi parent 50MHz is not configed\n");
+
+ return 0;
+err:
+ if (nfc->clk.nfi_clk)
+ kfree(nfc->clk.nfi_clk);
+ if (nfc->clk.snfi_clk)
+ kfree(nfc->clk.snfi_clk);
+ if (nfc->clk.ecc_clk)
+ kfree(nfc->clk.ecc_clk);
+ if (nfc->clk.snfi_clk_sel)
+ kfree(nfc->clk.snfi_clk_sel);
+ if (nfc->clk.snfi_parent_50m)
+ kfree(nfc->clk.snfi_parent_50m);
+
+ return ret;
+}
+
+U_BOOT_DRIVER(mtk_snand_drv) = {
+ .name = "mtk_snand",
+ .id = UCLASS_MTD,
+ .of_match = ic_of_match,
+ .ofdata_to_platdata = mtk_snand_ofdata_to_platdata,
+ .priv_auto_alloc_size = sizeof(struct nandx_nfc),
+};
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MTK Nand Flash Controller Driver");
+MODULE_AUTHOR("MediaTek");
--- /dev/null
+++ b/drivers/mtd/nandx/include/Nandx.mk
@@ -0,0 +1,16 @@
+#
+# Copyright (C) 2017 MediaTek Inc.
+# Licensed under either
+# BSD Licence, (see NOTICE for more details)
+# GNU General Public License, version 2.0, (see NOTICE for more details)
+#
+
+nandx-header-y += internal/nandx_core.h
+nandx-header-y += internal/nandx_errno.h
+nandx-header-y += internal/nandx_util.h
+nandx-header-$(NANDX_BBT_SUPPORT) += internal/bbt.h
+nandx-header-$(NANDX_SIMULATOR_SUPPORT) += simulator/nandx_os.h
+nandx-header-$(NANDX_CTP_SUPPORT) += ctp/nandx_os.h
+nandx-header-$(NANDX_LK_SUPPORT) += lk/nandx_os.h
+nandx-header-$(NANDX_KERNEL_SUPPORT) += kernel/nandx_os.h
+nandx-header-$(NANDX_UBOOT_SUPPORT) += uboot/nandx_os.h
--- /dev/null
+++ b/drivers/mtd/nandx/include/internal/bbt.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __BBT_H__
+#define __BBT_H__
+
+#define BBT_BLOCK_GOOD 0x03
+#define BBT_BLOCK_WORN 0x02
+#define BBT_BLOCK_RESERVED 0x01
+#define BBT_BLOCK_FACTORY_BAD 0x00
+
+#define BBT_INVALID_ADDR 0
+/* The maximum number of blocks to scan for a bbt */
+#define NAND_BBT_SCAN_MAXBLOCKS 4
+#define NAND_BBT_USE_FLASH 0x00020000
+#define NAND_BBT_NO_OOB 0x00040000
+
+/* Search good / bad pattern on the first and the second page */
+#define NAND_BBT_SCAN2NDPAGE 0x00008000
+/* Search good / bad pattern on the last page of the eraseblock */
+#define NAND_BBT_SCANLASTPAGE 0x00010000
+
+#define NAND_DRAM_BUF_DATABUF_ADDR (NAND_BUF_ADDR)
+
+struct bbt_pattern {
+ u8 *data;
+ int len;
+};
+
+struct bbt_desc {
+ struct bbt_pattern pattern;
+ u8 version;
+ u64 bbt_addr;/*0: invalid value; otherwise, valid value*/
+};
+
+struct bbt_manager {
+ /* main bbt descriptor and mirror descriptor */
+ struct bbt_desc desc[2];/* 0: main bbt; 1: mirror bbt */
+ int max_blocks;
+ u8 *bbt;
+};
+
+#define BBT_ENTRY_MASK 0x03
+#define BBT_ENTRY_SHIFT 2
+
+#define GET_BBT_LENGTH(blocks) (blocks >> 2)
+#define GET_ENTRY(block) ((block) >> BBT_ENTRY_SHIFT)
+#define GET_POSITION(block) (((block) & BBT_ENTRY_MASK) * 2)
+#define GET_MARK_VALUE(block, mark) \
+ (((mark) & BBT_ENTRY_MASK) << GET_POSITION(block))
+
+int scan_bbt(struct nandx_info *nand);
+
+int bbt_mark_bad(struct nandx_info *nand, off_t offset);
+
+int bbt_is_bad(struct nandx_info *nand, off_t offset);
+
+#endif /*__BBT_H__*/
--- /dev/null
+++ b/drivers/mtd/nandx/include/internal/nandx_core.h
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NANDX_CORE_H__
+#define __NANDX_CORE_H__
+
+/**
+ * mtk_ic_version - indicates specifical IC, IP need this to load some info
+ */
+enum mtk_ic_version {
+ NANDX_MT7622,
+};
+
+/**
+ * nandx_ioctl_cmd - operations supported by nandx
+ *
+ * @NFI_CTRL_DMA dma enable or not
+ * @NFI_CTRL_NFI_MODE customer/read/program/erase...
+ * @NFI_CTRL_ECC ecc enable or not
+ * @NFI_CTRL_ECC_MODE nfi/dma/pio
+ * @CHIP_CTRL_DRIVE_STRENGTH enum chip_ctrl_drive_strength
+ */
+enum nandx_ctrl_cmd {
+ CORE_CTRL_NAND_INFO,
+
+ NFI_CTRL_DMA,
+ NFI_CTRL_NFI_MODE,
+ NFI_CTRL_AUTOFORMAT,
+ NFI_CTRL_NFI_IRQ,
+ NFI_CTRL_PAGE_IRQ,
+ NFI_CTRL_RANDOMIZE,
+ NFI_CTRL_BAD_MARK_SWAP,
+
+ NFI_CTRL_ECC,
+ NFI_CTRL_ECC_MODE,
+ NFI_CTRL_ECC_CLOCK,
+ NFI_CTRL_ECC_IRQ,
+ NFI_CTRL_ECC_PAGE_IRQ,
+ NFI_CTRL_ECC_DECODE_MODE,
+
+ SNFI_CTRL_OP_MODE,
+ SNFI_CTRL_RX_MODE,
+ SNFI_CTRL_TX_MODE,
+ SNFI_CTRL_DELAY_MODE,
+
+ CHIP_CTRL_OPS_CACHE,
+ CHIP_CTRL_OPS_MULTI,
+ CHIP_CTRL_PSLC_MODE,
+ CHIP_CTRL_DRIVE_STRENGTH,
+ CHIP_CTRL_DDR_MODE,
+ CHIP_CTRL_ONDIE_ECC,
+ CHIP_CTRL_TIMING_MODE
+};
+
+enum snfi_ctrl_op_mode {
+ SNFI_CUSTOM_MODE,
+ SNFI_AUTO_MODE,
+ SNFI_MAC_MODE
+};
+
+enum snfi_ctrl_rx_mode {
+ SNFI_RX_111,
+ SNFI_RX_112,
+ SNFI_RX_114,
+ SNFI_RX_122,
+ SNFI_RX_144
+};
+
+enum snfi_ctrl_tx_mode {
+ SNFI_TX_111,
+ SNFI_TX_114,
+};
+
+enum chip_ctrl_drive_strength {
+ CHIP_DRIVE_NORMAL,
+ CHIP_DRIVE_HIGH,
+ CHIP_DRIVE_MIDDLE,
+ CHIP_DRIVE_LOW
+};
+
+enum chip_ctrl_timing_mode {
+ CHIP_TIMING_MODE0,
+ CHIP_TIMING_MODE1,
+ CHIP_TIMING_MODE2,
+ CHIP_TIMING_MODE3,
+ CHIP_TIMING_MODE4,
+ CHIP_TIMING_MODE5,
+};
+
+/**
+ * nandx_info - basic information
+ */
+struct nandx_info {
+ u32 max_io_count;
+ u32 min_write_pages;
+ u32 plane_num;
+ u32 oob_size;
+ u32 page_parity_size;
+ u32 page_size;
+ u32 block_size;
+ u64 total_size;
+ u32 fdm_reg_size;
+ u32 fdm_ecc_size;
+ u32 ecc_strength;
+ u32 sector_size;
+};
+
+/**
+ * nfi_resource - the resource needed by nfi & ecc to do initialization
+ */
+struct nfi_resource {
+ int ic_ver;
+ void *dev;
+
+ void *ecc_regs;
+ int ecc_irq_id;
+
+ void *nfi_regs;
+ int nfi_irq_id;
+
+ u32 clock_1x;
+ u32 *clock_2x;
+ int clock_2x_num;
+
+ int min_oob_req;
+};
+
+/**
+ * nandx_init - init all related modules below
+ *
+ * @res: basic resource of the project
+ *
+ * return 0 if init success, otherwise return negative error code
+ */
+int nandx_init(struct nfi_resource *res);
+
+/**
+ * nandx_exit - release resource those that obtained in init flow
+ */
+void nandx_exit(void);
+
+/**
+ * nandx_read - read data from nand this function can read data and related
+ * oob from specifical address
+ * if do multi_ops, set one operation per time, and call nandx_sync at last
+ * in multi mode, not support page partial read
+ * oob not support partial read
+ *
+ * @data: buf to receive data from nand
+ * @oob: buf to receive oob data from nand which related to data page
+ * length of @oob should oob size aligned, oob not support partial read
+ * @offset: offset address on the whole flash
+ * @len: the length of @data that need to read
+ *
+ * if read success return 0, otherwise return negative error code
+ */
+int nandx_read(u8 *data, u8 *oob, u64 offset, size_t len);
+
+/**
+ * nandx_write - write data to nand
+ * this function can write data and related oob to specifical address
+ * if do multi_ops, set one operation per time, and call nandx_sync at last
+ *
+ * @data: source data to be written to nand,
+ * for multi operation, the length of @data should be page size aliged
+ * @oob: source oob which related to data page to be written to nand,
+ * length of @oob should oob size aligned
+ * @offset: offset address on the whole flash, the value should be start address
+ * of a page
+ * @len: the length of @data that need to write,
+ * for multi operation, the len should be page size aliged
+ *
+ * if write success return 0, otherwise return negative error code
+ * if return value > 0, it indicates that how many pages still need to write,
+ * and data has not been written to nand
+ * please call nandx_sync after pages alligned $nandx_info.min_write_pages
+ */
+int nandx_write(u8 *data, u8 *oob, u64 offset, size_t len);
+
+/**
+ * nandx_erase - erase an area of nand
+ * if do multi_ops, set one operation per time, and call nandx_sync at last
+ *
+ * @offset: offset address on the flash
+ * @len: erase length which should be block size aligned
+ *
+ * if erase success return 0, otherwise return negative error code
+ */
+int nandx_erase(u64 offset, size_t len);
+
+/**
+ * nandx_sync - sync all operations to nand
+ * when do multi_ops, this function will be called at last operation
+ * when write data, if number of pages not alligned
+ * by $nandx_info.min_write_pages, this interface could be called to do
+ * force write, 0xff will be padded to blanked pages.
+ */
+int nandx_sync(void);
+
+/**
+ * nandx_is_bad_block - check if the block is bad
+ * only check the flag that marked by the flash vendor
+ *
+ * @offset: offset address on the whole flash
+ *
+ * return true if the block is bad, otherwise return false
+ */
+bool nandx_is_bad_block(u64 offset);
+
+/**
+ * nandx_ioctl - set/get property of nand chip
+ *
+ * @cmd: parameter that defined in enum nandx_ioctl_cmd
+ * @arg: operate parameter
+ *
+ * return 0 if operate success, otherwise return negative error code
+ */
+int nandx_ioctl(int cmd, void *arg);
+
+/**
+ * nandx_suspend - suspend nand, and store some data
+ *
+ * return 0 if suspend success, otherwise return negative error code
+ */
+int nandx_suspend(void);
+
+/**
+ * nandx_resume - resume nand, and replay some data
+ *
+ * return 0 if resume success, otherwise return negative error code
+ */
+int nandx_resume(void);
+
+#ifdef NANDX_UNIT_TEST
+/**
+ * nandx_unit_test - unit test
+ *
+ * @offset: offset address on the whole flash
+ * @len: should be not larger than a block size, we only test a block per time
+ *
+ * return 0 if test success, otherwise return negative error code
+ */
+int nandx_unit_test(u64 offset, size_t len);
+#endif
+
+#endif /* __NANDX_CORE_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/include/internal/nandx_errno.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NANDX_ERRNO_H__
+#define __NANDX_ERRNO_H__
+
+#ifndef EIO
+#define EIO 5 /* I/O error */
+#define ENOMEM 12 /* Out of memory */
+#define EFAULT 14 /* Bad address */
+#define EBUSY 16 /* Device or resource busy */
+#define ENODEV 19 /* No such device */
+#define EINVAL 22 /* Invalid argument */
+#define ENOSPC 28 /* No space left on device */
+/* Operation not supported on transport endpoint */
+#define EOPNOTSUPP 95
+#define ETIMEDOUT 110 /* Connection timed out */
+#endif
+
+#define ENANDFLIPS 1024 /* Too many bitflips, uncorrected */
+#define ENANDREAD 1025 /* Read fail, can't correct */
+#define ENANDWRITE 1026 /* Write fail */
+#define ENANDERASE 1027 /* Erase fail */
+#define ENANDBAD 1028 /* Bad block */
+#define ENANDWP 1029
+
+#define IS_NAND_ERR(err) ((err) >= -ENANDBAD && (err) <= -ENANDFLIPS)
+
+#ifndef MAX_ERRNO
+#define MAX_ERRNO 4096
+#define ERR_PTR(errno) ((void *)((long)errno))
+#define PTR_ERR(ptr) ((long)(ptr))
+#define IS_ERR(ptr) ((unsigned long)(ptr) > (unsigned long)-MAX_ERRNO)
+#endif
+
+#endif /* __NANDX_ERRNO_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/include/internal/nandx_util.h
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NANDX_UTIL_H__
+#define __NANDX_UTIL_H__
+
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned int u32;
+typedef unsigned long long u64;
+
+enum nand_irq_return {
+ NAND_IRQ_NONE,
+ NAND_IRQ_HANDLED,
+};
+
+enum nand_dma_operation {
+ NDMA_FROM_DEV,
+ NDMA_TO_DEV,
+};
+
+
+/*
+ * Compatible function
+ * used for preloader/lk/kernel environment
+ */
+#include "nandx_os.h"
+#include "nandx_errno.h"
+
+#ifndef BIT
+#define BIT(a) (1 << (a))
+#endif
+
+#ifndef min_t
+#define min_t(type, x, y) ({ \
+ type __min1 = (x); \
+ type __min2 = (y); \
+ __min1 < __min2 ? __min1 : __min2; })
+
+#define max_t(type, x, y) ({ \
+ type __max1 = (x); \
+ type __max2 = (y); \
+ __max1 > __max2 ? __max1 : __max2; })
+#endif
+
+#ifndef GENMASK
+#define GENMASK(h, l) \
+ (((~0UL) << (l)) & (~0UL >> ((sizeof(unsigned long) * 8) - 1 - (h))))
+#endif
+
+#ifndef __weak
+#define __weak __attribute__((__weak__))
+#endif
+
+#ifndef __packed
+#define __packed __attribute__((__packed__))
+#endif
+
+#ifndef KB
+#define KB(x) ((x) << 10)
+#define MB(x) (KB(x) << 10)
+#define GB(x) (MB(x) << 10)
+#endif
+
+#ifndef offsetof
+#define offsetof(type, member) ((size_t)&((type *)0)->member)
+#endif
+
+#ifndef NULL
+#define NULL (void *)0
+#endif
+static inline u32 nandx_popcount(u32 x)
+{
+ x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
+ x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+ x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);
+ x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);
+ x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF);
+
+ return x;
+}
+
+#ifndef zero_popcount
+#define zero_popcount(x) (32 - nandx_popcount(x))
+#endif
+
+#ifndef do_div
+#define do_div(n, base) \
+ ({ \
+ u32 __base = (base); \
+ u32 __rem; \
+ __rem = ((u64)(n)) % __base; \
+ (n) = ((u64)(n)) / __base; \
+ __rem; \
+ })
+#endif
+
+#define div_up(x, y) \
+ ({ \
+ u64 __temp = ((x) + (y) - 1); \
+ do_div(__temp, (y)); \
+ __temp; \
+ })
+
+#define div_down(x, y) \
+ ({ \
+ u64 __temp = (x); \
+ do_div(__temp, (y)); \
+ __temp; \
+ })
+
+#define div_round_up(x, y) (div_up(x, y) * (y))
+#define div_round_down(x, y) (div_down(x, y) * (y))
+
+#define reminder(x, y) \
+ ({ \
+ u64 __temp = (x); \
+ do_div(__temp, (y)); \
+ })
+
+#ifndef round_up
+#define round_up(x, y) ((((x) - 1) | ((y) - 1)) + 1)
+#define round_down(x, y) ((x) & ~((y) - 1))
+#endif
+
+#ifndef readx_poll_timeout_atomic
+#define readx_poll_timeout_atomic(op, addr, val, cond, delay_us, timeout_us) \
+ ({ \
+ u64 end = get_current_time_us() + timeout_us; \
+ for (;;) { \
+ u64 now = get_current_time_us(); \
+ (val) = op(addr); \
+ if (cond) \
+ break; \
+ if (now > end) { \
+ (val) = op(addr); \
+ break; \
+ } \
+ } \
+ (cond) ? 0 : -ETIMEDOUT; \
+ })
+
+#define readl_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us) \
+ readx_poll_timeout_atomic(readl, addr, val, cond, delay_us, timeout_us)
+#define readw_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us) \
+ readx_poll_timeout_atomic(readw, addr, val, cond, delay_us, timeout_us)
+#define readb_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us) \
+ readx_poll_timeout_atomic(readb, addr, val, cond, delay_us, timeout_us)
+#endif
+
+struct nandx_split64 {
+ u64 head;
+ size_t head_len;
+ u64 body;
+ size_t body_len;
+ u64 tail;
+ size_t tail_len;
+};
+
+struct nandx_split32 {
+ u32 head;
+ u32 head_len;
+ u32 body;
+ u32 body_len;
+ u32 tail;
+ u32 tail_len;
+};
+
+#define nandx_split(split, offset, len, val, align) \
+ do { \
+ (split)->head = (offset); \
+ (val) = div_round_down((offset), (align)); \
+ (val) = (align) - ((offset) - (val)); \
+ if ((val) == (align)) \
+ (split)->head_len = 0; \
+ else if ((val) > (len)) \
+ (split)->head_len = len; \
+ else \
+ (split)->head_len = val; \
+ (split)->body = (offset) + (split)->head_len; \
+ (split)->body_len = div_round_down((len) - \
+ (split)->head_len,\
+ (align)); \
+ (split)->tail = (split)->body + (split)->body_len; \
+ (split)->tail_len = (len) - (split)->head_len - \
+ (split)->body_len; \
+ } while (0)
+
+#ifndef container_of
+#define container_of(ptr, type, member) \
+ ({const __typeof__(((type *)0)->member) * __mptr = (ptr); \
+ (type *)((char *)__mptr - offsetof(type, member)); })
+#endif
+
+static inline u32 nandx_cpu_to_be32(u32 val)
+{
+ u32 temp = 1;
+ u8 *p_temp = (u8 *)&temp;
+
+ if (*p_temp)
+ return ((val & 0xff) << 24) | ((val & 0xff00) << 8) |
+ ((val >> 8) & 0xff00) | ((val >> 24) & 0xff);
+
+ return val;
+}
+
+static inline void nandx_set_bits32(unsigned long addr, u32 mask,
+ u32 val)
+{
+ u32 temp = readl((void *)addr);
+
+ temp &= ~(mask);
+ temp |= val;
+ writel(temp, (void *)addr);
+}
+
+#endif /* __NANDX_UTIL_H__ */
--- /dev/null
+++ b/drivers/mtd/nandx/include/uboot/nandx_os.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 MediaTek Inc.
+ * Licensed under either
+ * BSD Licence, (see NOTICE for more details)
+ * GNU General Public License, version 2.0, (see NOTICE for more details)
+ */
+
+#ifndef __NANDX_OS_H__
+#define __NANDX_OS_H__
+
+#include <common.h>
+#include <dm.h>
+#include <clk.h>
+#include <asm/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/bitops.h>
+#include <linux/kernel.h>
+#include <linux/compiler-gcc.h>
+
+#define NANDX_BULK_IO_USE_DRAM 0
+
+#define nandx_event_create() NULL
+#define nandx_event_destroy(event)
+#define nandx_event_complete(event)
+#define nandx_event_init(event)
+#define nandx_event_wait_complete(event, timeout) true
+
+#define nandx_irq_register(dev, irq, irq_handler, name, data) NULL
+
+static inline void *mem_alloc(u32 count, u32 size)
+{
+ return kmalloc(count * size, GFP_KERNEL | __GFP_ZERO);
+}
+
+static inline void mem_free(void *mem)
+{
+ kfree(mem);
+}
+
+static inline u64 get_current_time_us(void)
+{
+ return timer_get_us();
+}
+
+static inline u32 nandx_dma_map(void *dev, void *buf, u64 len,
+ enum nand_dma_operation op)
+{
+ unsigned long addr = (unsigned long)buf;
+ u64 size;
+
+ size = ALIGN(len, ARCH_DMA_MINALIGN);
+
+ if (op == NDMA_FROM_DEV)
+ invalidate_dcache_range(addr, addr + size);
+ else
+ flush_dcache_range(addr, addr + size);
+
+ return addr;
+}
+
+static inline void nandx_dma_unmap(void *dev, void *buf, void *addr,
+ u64 len, enum nand_dma_operation op)
+{
+ u64 size;
+
+ size = ALIGN(len, ARCH_DMA_MINALIGN);
+
+ if (op != NDMA_FROM_DEV)
+ invalidate_dcache_range((unsigned long)addr, addr + size);
+ else
+ flush_dcache_range((unsigned long)addr, addr + size);
+
+ return addr;
+}
+
+#endif /* __NANDX_OS_H__ */
--- a/include/configs/mt7622.h
+++ b/include/configs/mt7622.h
@@ -11,6 +11,31 @@
#include <linux/sizes.h>
+/* SPI Nand */
+#if defined(CONFIG_MTD_RAW_NAND)
+#define CONFIG_SYS_MAX_NAND_DEVICE 1
+#define CONFIG_SYS_NAND_BASE 0x1100d000
+
+#define ENV_BOOT_READ_IMAGE \
+ "boot_rd_img=" \
+ "nand read 0x4007ff28 0x380000 0x1400000" \
+ ";iminfo 0x4007ff28 \0"
+
+#define ENV_BOOT_WRITE_IMAGE \
+ "boot_wr_img=" \
+ "nand write 0x4007ff28 0x380000 0x1400000" \
+ ";iminfo 0x4007ff28 \0"
+
+#define ENV_BOOT_CMD \
+ "mtk_boot=run boot_rd_img;bootm;\0"
+
+#define CONFIG_EXTRA_ENV_SETTINGS \
+ ENV_BOOT_READ_IMAGE \
+ ENV_BOOT_CMD \
+ "bootcmd=run mtk_boot;\0"
+
+#endif
+
#define CONFIG_SYS_MAXARGS 8
#define CONFIG_SYS_BOOTM_LEN SZ_64M
#define CONFIG_SYS_CBSIZE SZ_1K