From: John Crispin Date: Mon, 4 May 2020 14:31:19 +0000 (+0200) Subject: mediatek: add uboot X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=b9cf9ebdb7aab1be0b9ee9825c71c313259d22da;p=openwrt%2Fstaging%2Fmkresin.git mediatek: add uboot Signed-off-by: John Crispin --- diff --git a/package/boot/uboot-mediatek/Makefile b/package/boot/uboot-mediatek/Makefile new file mode 100644 index 0000000000..a8256529bf --- /dev/null +++ b/package/boot/uboot-mediatek/Makefile @@ -0,0 +1,41 @@ +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_VERSION:=2020.04 +PKG_RELEASE:=1 +PKG_HASH:=fe732aaf037d9cc3c0909bad8362af366ae964bbdac6913a34081ff4ad565372 + +include $(INCLUDE_DIR)/u-boot.mk +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/host-build.mk + +define U-Boot/Default + BUILD_TARGET:=mediatek + UBOOT_IMAGE:=u-boot-mtk.bin + DEFAULT:=y +endef + +define U-Boot/mt7629 + NAME:=MT7629 + BUILD_SUBTARGET:=mt7629 + UBOOT_CONFIG:=mt7629_rfb +endef + +define U-Boot/mt7622 + NAME:=MT7622 + BUILD_SUBTARGET:=mt7622 + UBOOT_CONFIG:=mt7622_rfb +endef + +UBOOT_TARGETS := mt7629 mt7622 + +UBOOT_MAKE_FLAGS += $(UBOOT_IMAGE) + +Build/Exports:=$(Host/Exports) + +define Build/InstallDev + $(INSTALL_DIR) $(STAGING_DIR_IMAGE) + $(INSTALL_BIN) $(PKG_BUILD_DIR)/$(UBOOT_IMAGE) $(STAGING_DIR_IMAGE)/$(BUILD_VARIANT)-$(UBOOT_IMAGE) +endef + +$(eval $(call BuildPackage/U-Boot)) diff --git a/package/boot/uboot-mediatek/patches/001-eth-mtk-add-mt7531-switch.patch b/package/boot/uboot-mediatek/patches/001-eth-mtk-add-mt7531-switch.patch new file mode 100644 index 0000000000..ced1ca9a15 --- /dev/null +++ b/package/boot/uboot-mediatek/patches/001-eth-mtk-add-mt7531-switch.patch @@ -0,0 +1,1074 @@ +From patchwork Tue Feb 18 08:49:37 2020 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +X-Patchwork-Submitter: Landen Chao +X-Patchwork-Id: 1239956 +X-Patchwork-Delegate: trini@ti.com +Return-Path: +X-Original-To: incoming@patchwork.ozlabs.org +Delivered-To: patchwork-incoming@bilbo.ozlabs.org +Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) + smtp.mailfrom=lists.denx.de + (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; + helo=phobos.denx.de; + envelope-from=u-boot-bounces@lists.denx.de; + receiver=) +Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) + header.from=mediatek.com +Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; + unprotected) header.d=mediatek.com header.i=@mediatek.com + header.a=rsa-sha256 header.s=dk header.b=NQKfnebM; + dkim-atps=neutral +Received: from phobos.denx.de (phobos.denx.de + [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange X25519 server-signature RSA-PSS (4096 bits)) + (No client certificate requested) + by ozlabs.org (Postfix) with ESMTPS id 48MJX54ymTz9sRf + for ; + Tue, 18 Feb 2020 22:28:53 +1100 (AEDT) +Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) + by phobos.denx.de (Postfix) with ESMTP id A68D8810D8; + Tue, 18 Feb 2020 12:28:47 +0100 (CET) +Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) + header.from=mediatek.com +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de +Authentication-Results: phobos.denx.de; dkim=pass (1024-bit key; + unprotected) header.d=mediatek.com header.i=@mediatek.com + header.b="NQKfnebM"; dkim-atps=neutral +Received: by phobos.denx.de (Postfix, from userid 109) + id 767F881217; Tue, 18 Feb 2020 09:50:00 +0100 (CET) +X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de +X-Spam-Level: +X-Spam-Status: No, score=0.5 required=5.0 tests=BAYES_00,DKIM_SIGNED, + DKIM_VALID,DKIM_VALID_AU,MIME_BASE64_TEXT,RDNS_NONE,SPF_HELO_NONE, + UNPARSEABLE_RELAY,URIBL_BLOCKED autolearn=no autolearn_force=no + version=3.4.2 +Received: from mailgw01.mediatek.com (unknown [210.61.82.183]) + by phobos.denx.de (Postfix) with ESMTP id 40ADE811FB + for ; Tue, 18 Feb 2020 09:49:46 +0100 (CET) +Authentication-Results: phobos.denx.de; + dmarc=pass (p=none dis=none) header.from=mediatek.com +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=landen.chao@mediatek.com +X-UUID: ddaa8deb3c7d4a24ba5cbcb30775fbfd-20200218 +DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; + d=mediatek.com; s=dk; + h=Content-Transfer-Encoding:Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:CC:To:From; + bh=qvWp4DdC1p5SZD1XJPV3kwdXTU9lBi4hbWNZQoO2oLI=; + b=NQKfnebM0mIfPJuncOxa6aNY+g5wFOnlozbm/o8ViKaVNbkd8YSB0ISWOerj8lALM7E7sSP797r514G3R4EAQPPvuAm5B2wi2VuYKssTxHkvYJvCrALXp4wmQtGF+gJytfdWuHx6Np0wfbjTsLqw+JUtcDi/0jAa8TQ5y9UHr9Q=; +X-UUID: ddaa8deb3c7d4a24ba5cbcb30775fbfd-20200218 +Received: from mtkcas07.mediatek.inc [(172.21.101.84)] by + mailgw01.mediatek.com (envelope-from ) + (Cellopoint E-mail Firewall v4.1.10 Build 0809 with TLS) + with ESMTP id 827129897; Tue, 18 Feb 2020 16:49:43 +0800 +Received: from MTKCAS06.mediatek.inc (172.21.101.30) by + mtkmbs05n1.mediatek.inc (172.21.101.15) with Microsoft SMTP Server + (TLS) id 15.0.1395.4; Tue, 18 Feb 2020 16:48:45 +0800 +Received: from mtksdccf07.mediatek.inc (172.21.84.99) by MTKCAS06.mediatek.inc + (172.21.101.73) with Microsoft SMTP Server id 15.0.1395.4 via + Frontend Transport; Tue, 18 Feb 2020 16:47:41 +0800 +From: Landen Chao +To: Ryder Lee , Weijie Gao , + GSS_MTK_Uboot_upstream , + Joe Hershberger , +CC: , , + , +Subject: [U-Boot 1/1] eth: mtk-eth: add mt7531 switch support in mediatek eth + driver +Date: Tue, 18 Feb 2020 16:49:37 +0800 +Message-ID: +X-Mailer: git-send-email 2.18.0 +In-Reply-To: +References: +MIME-Version: 1.0 +X-MTK: N +X-Mailman-Approved-At: Tue, 18 Feb 2020 12:28:45 +0100 +X-BeenThere: u-boot@lists.denx.de +X-Mailman-Version: 2.1.30rc1 +Precedence: list +List-Id: U-Boot discussion +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Errors-To: u-boot-bounces@lists.denx.de +Sender: "U-Boot" +X-Virus-Scanned: clamav-milter 0.102.1 at phobos.denx.de +X-Virus-Status: Clean + +mt7531 is a 7-ports switch with 5 embedded giga phys, and uses the same +MAC design of mt7530. The cpu port6 supports SGMII only. The cpu port5 +supports RGMII or SGMII in different model. + +mt7531 is connected to mt7622 via both RGMII and SGMII interfaces. +In this patch, mt7531 cpu port5 or port6 is configured to maximum +capability to align CPU MAC setting. + +The dts has been committed in the commit 6efa450565cdc ("arm: dts: +mediatek: add ethernet and sgmii dts node for mt7622") + +Signed-off-by: Landen Chao +Tested-by: Frank Wunderlich +--- + drivers/net/mtk_eth.c | 580 +++++++++++++++++++++++++++++++++--------- + drivers/net/mtk_eth.h | 124 ++++++++- + 2 files changed, 577 insertions(+), 127 deletions(-) + +diff --git a/drivers/net/mtk_eth.c b/drivers/net/mtk_eth.c +index edfa5d1ce8..f7d34a2e6b 100644 +--- a/drivers/net/mtk_eth.c ++++ b/drivers/net/mtk_eth.c +@@ -30,10 +30,12 @@ + #define RX_TOTAL_BUF_SIZE (NUM_RX_DESC * PKTSIZE_ALIGN) + #define TOTAL_PKT_BUF_SIZE (TX_TOTAL_BUF_SIZE + RX_TOTAL_BUF_SIZE) + +-#define MT7530_NUM_PHYS 5 +-#define MT7530_DFL_SMI_ADDR 31 ++#define MT753X_NUM_PHYS 5 ++#define MT753X_NUM_PORTS 7 ++#define MT753X_DFL_SMI_ADDR 31 ++#define MT753X_SMI_ADDR_MASK 0x1f + +-#define MT7530_PHY_ADDR(base, addr) \ ++#define MT753X_PHY_ADDR(base, addr) \ + (((base) + (addr)) & 0x1f) + + #define GDMA_FWD_TO_CPU \ +@@ -131,7 +133,8 @@ struct pdma_txdesc { + + enum mtk_switch { + SW_NONE, +- SW_MT7530 ++ SW_MT7530, ++ SW_MT7531 + }; + + enum mtk_soc { +@@ -173,8 +176,8 @@ struct mtk_eth_priv { + + enum mtk_switch sw; + int (*switch_init)(struct mtk_eth_priv *priv); +- u32 mt7530_smi_addr; +- u32 mt7530_phy_base; ++ u32 mt753x_smi_addr; ++ u32 mt753x_phy_base; + + struct gpio_desc rst_gpio; + int mcm; +@@ -349,6 +352,174 @@ static int mtk_mmd_ind_write(struct mtk_eth_priv *priv, u8 addr, u8 devad, + return priv->mii_write(priv, addr, MII_MMD_ADDR_DATA_REG, val); + } + ++/* ++ * MT7530 Internal Register Address Bits ++ * ------------------------------------------------------------------- ++ * | 15 14 13 12 11 10 9 8 7 6 | 5 4 3 2 | 1 0 | ++ * |----------------------------------------|---------------|--------| ++ * | Page Address | Reg Address | Unused | ++ * ------------------------------------------------------------------- ++ */ ++ ++static int mt753x_reg_read(struct mtk_eth_priv *priv, u32 reg, u32 *data) ++{ ++ int ret, low_word, high_word; ++ ++ /* Write page address */ ++ ret = mtk_mii_write(priv, priv->mt753x_smi_addr, 0x1f, reg >> 6); ++ if (ret) ++ return ret; ++ ++ /* Read low word */ ++ low_word = mtk_mii_read(priv, priv->mt753x_smi_addr, (reg >> 2) & 0xf); ++ if (low_word < 0) ++ return low_word; ++ ++ /* Read high word */ ++ high_word = mtk_mii_read(priv, priv->mt753x_smi_addr, 0x10); ++ if (high_word < 0) ++ return high_word; ++ ++ if (data) ++ *data = ((u32)high_word << 16) | (low_word & 0xffff); ++ ++ return 0; ++} ++ ++static int mt753x_reg_write(struct mtk_eth_priv *priv, u32 reg, u32 data) ++{ ++ int ret; ++ ++ /* Write page address */ ++ ret = mtk_mii_write(priv, priv->mt753x_smi_addr, 0x1f, reg >> 6); ++ if (ret) ++ return ret; ++ ++ /* Write low word */ ++ ret = mtk_mii_write(priv, priv->mt753x_smi_addr, (reg >> 2) & 0xf, ++ data & 0xffff); ++ if (ret) ++ return ret; ++ ++ /* Write high word */ ++ return mtk_mii_write(priv, priv->mt753x_smi_addr, 0x10, data >> 16); ++} ++ ++static void mt753x_reg_rmw(struct mtk_eth_priv *priv, u32 reg, u32 clr, ++ u32 set) ++{ ++ u32 val; ++ ++ mt753x_reg_read(priv, reg, &val); ++ val &= ~clr; ++ val |= set; ++ mt753x_reg_write(priv, reg, val); ++} ++ ++/* Indirect MDIO clause 22/45 access */ ++static int mt7531_mii_rw(struct mtk_eth_priv *priv, int phy, int reg, u16 data, ++ u32 cmd, u32 st) ++{ ++ ulong timeout; ++ u32 val, timeout_ms; ++ int ret = 0; ++ ++ val = (st << MDIO_ST_S) | ++ ((cmd << MDIO_CMD_S) & MDIO_CMD_M) | ++ ((phy << MDIO_PHY_ADDR_S) & MDIO_PHY_ADDR_M) | ++ ((reg << MDIO_REG_ADDR_S) & MDIO_REG_ADDR_M); ++ ++ if (cmd == MDIO_CMD_WRITE || cmd == MDIO_CMD_ADDR) ++ val |= data & MDIO_RW_DATA_M; ++ ++ mt753x_reg_write(priv, MT7531_PHY_IAC, val | PHY_ACS_ST); ++ ++ timeout_ms = 100; ++ timeout = get_timer(0); ++ while (1) { ++ mt753x_reg_read(priv, MT7531_PHY_IAC, &val); ++ ++ if ((val & PHY_ACS_ST) == 0) ++ break; ++ ++ if (get_timer(timeout) > timeout_ms) ++ return -ETIMEDOUT; ++ } ++ ++ if (cmd == MDIO_CMD_READ || cmd == MDIO_CMD_READ_C45) { ++ mt753x_reg_read(priv, MT7531_PHY_IAC, &val); ++ ret = val & MDIO_RW_DATA_M; ++ } ++ ++ return ret; ++} ++ ++static int mt7531_mii_ind_read(struct mtk_eth_priv *priv, u8 phy, u8 reg) ++{ ++ u8 phy_addr; ++ ++ if (phy >= MT753X_NUM_PHYS) ++ return -EINVAL; ++ ++ phy_addr = MT753X_PHY_ADDR(priv->mt753x_phy_base, phy); ++ ++ return mt7531_mii_rw(priv, phy_addr, reg, 0, MDIO_CMD_READ, ++ MDIO_ST_C22); ++} ++ ++static int mt7531_mii_ind_write(struct mtk_eth_priv *priv, u8 phy, u8 reg, ++ u16 val) ++{ ++ u8 phy_addr; ++ ++ if (phy >= MT753X_NUM_PHYS) ++ return -EINVAL; ++ ++ phy_addr = MT753X_PHY_ADDR(priv->mt753x_phy_base, phy); ++ ++ return mt7531_mii_rw(priv, phy_addr, reg, val, MDIO_CMD_WRITE, ++ MDIO_ST_C22); ++} ++ ++int mt7531_mmd_ind_read(struct mtk_eth_priv *priv, u8 addr, u8 devad, u16 reg) ++{ ++ u8 phy_addr; ++ int ret; ++ ++ if (addr >= MT753X_NUM_PHYS) ++ return -EINVAL; ++ ++ phy_addr = MT753X_PHY_ADDR(priv->mt753x_phy_base, addr); ++ ++ ret = mt7531_mii_rw(priv, phy_addr, devad, reg, MDIO_CMD_ADDR, ++ MDIO_ST_C45); ++ if (ret) ++ return ret; ++ ++ return mt7531_mii_rw(priv, phy_addr, devad, 0, MDIO_CMD_READ_C45, ++ MDIO_ST_C45); ++} ++ ++static int mt7531_mmd_ind_write(struct mtk_eth_priv *priv, u8 addr, u8 devad, ++ u16 reg, u16 val) ++{ ++ u8 phy_addr; ++ int ret; ++ ++ if (addr >= MT753X_NUM_PHYS) ++ return 0; ++ ++ phy_addr = MT753X_PHY_ADDR(priv->mt753x_phy_base, addr); ++ ++ ret = mt7531_mii_rw(priv, phy_addr, devad, reg, MDIO_CMD_ADDR, ++ MDIO_ST_C45); ++ if (ret) ++ return ret; ++ ++ return mt7531_mii_rw(priv, phy_addr, devad, val, MDIO_CMD_WRITE, ++ MDIO_ST_C45); ++} ++ + static int mtk_mdio_read(struct mii_dev *bus, int addr, int devad, int reg) + { + struct mtk_eth_priv *priv = bus->priv; +@@ -387,6 +558,12 @@ static int mtk_mdio_register(struct udevice *dev) + priv->mmd_read = mtk_mmd_ind_read; + priv->mmd_write = mtk_mmd_ind_write; + break; ++ case SW_MT7531: ++ priv->mii_read = mt7531_mii_ind_read; ++ priv->mii_write = mt7531_mii_ind_write; ++ priv->mmd_read = mt7531_mmd_ind_read; ++ priv->mmd_write = mt7531_mmd_ind_write; ++ break; + default: + priv->mii_read = mtk_mii_read; + priv->mii_write = mtk_mii_write; +@@ -410,75 +587,18 @@ static int mtk_mdio_register(struct udevice *dev) + return 0; + } + +-/* +- * MT7530 Internal Register Address Bits +- * ------------------------------------------------------------------- +- * | 15 14 13 12 11 10 9 8 7 6 | 5 4 3 2 | 1 0 | +- * |----------------------------------------|---------------|--------| +- * | Page Address | Reg Address | Unused | +- * ------------------------------------------------------------------- +- */ +- +-static int mt7530_reg_read(struct mtk_eth_priv *priv, u32 reg, u32 *data) ++static int mt753x_core_reg_read(struct mtk_eth_priv *priv, u32 reg) + { +- int ret, low_word, high_word; ++ u8 phy_addr = MT753X_PHY_ADDR(priv->mt753x_phy_base, 0); + +- /* Write page address */ +- ret = mtk_mii_write(priv, priv->mt7530_smi_addr, 0x1f, reg >> 6); +- if (ret) +- return ret; +- +- /* Read low word */ +- low_word = mtk_mii_read(priv, priv->mt7530_smi_addr, (reg >> 2) & 0xf); +- if (low_word < 0) +- return low_word; +- +- /* Read high word */ +- high_word = mtk_mii_read(priv, priv->mt7530_smi_addr, 0x10); +- if (high_word < 0) +- return high_word; +- +- if (data) +- *data = ((u32)high_word << 16) | (low_word & 0xffff); +- +- return 0; ++ return priv->mmd_read(priv, phy_addr, 0x1f, reg); + } + +-static int mt7530_reg_write(struct mtk_eth_priv *priv, u32 reg, u32 data) ++static void mt753x_core_reg_write(struct mtk_eth_priv *priv, u32 reg, u32 val) + { +- int ret; +- +- /* Write page address */ +- ret = mtk_mii_write(priv, priv->mt7530_smi_addr, 0x1f, reg >> 6); +- if (ret) +- return ret; ++ u8 phy_addr = MT753X_PHY_ADDR(priv->mt753x_phy_base, 0); + +- /* Write low word */ +- ret = mtk_mii_write(priv, priv->mt7530_smi_addr, (reg >> 2) & 0xf, +- data & 0xffff); +- if (ret) +- return ret; +- +- /* Write high word */ +- return mtk_mii_write(priv, priv->mt7530_smi_addr, 0x10, data >> 16); +-} +- +-static void mt7530_reg_rmw(struct mtk_eth_priv *priv, u32 reg, u32 clr, +- u32 set) +-{ +- u32 val; +- +- mt7530_reg_read(priv, reg, &val); +- val &= ~clr; +- val |= set; +- mt7530_reg_write(priv, reg, val); +-} +- +-static void mt7530_core_reg_write(struct mtk_eth_priv *priv, u32 reg, u32 val) +-{ +- u8 phy_addr = MT7530_PHY_ADDR(priv->mt7530_phy_base, 0); +- +- mtk_mmd_ind_write(priv, phy_addr, 0x1f, reg, val); ++ priv->mmd_write(priv, phy_addr, 0x1f, reg, val); + } + + static int mt7530_pad_clk_setup(struct mtk_eth_priv *priv, int mode) +@@ -496,46 +616,46 @@ static int mt7530_pad_clk_setup(struct mtk_eth_priv *priv, int mode) + } + + /* Disable MT7530 core clock */ +- mt7530_core_reg_write(priv, CORE_TRGMII_GSW_CLK_CG, 0); ++ mt753x_core_reg_write(priv, CORE_TRGMII_GSW_CLK_CG, 0); + + /* Disable MT7530 PLL */ +- mt7530_core_reg_write(priv, CORE_GSWPLL_GRP1, ++ mt753x_core_reg_write(priv, CORE_GSWPLL_GRP1, + (2 << RG_GSWPLL_POSDIV_200M_S) | + (32 << RG_GSWPLL_FBKDIV_200M_S)); + + /* For MT7530 core clock = 500Mhz */ +- mt7530_core_reg_write(priv, CORE_GSWPLL_GRP2, ++ mt753x_core_reg_write(priv, CORE_GSWPLL_GRP2, + (1 << RG_GSWPLL_POSDIV_500M_S) | + (25 << RG_GSWPLL_FBKDIV_500M_S)); + + /* Enable MT7530 PLL */ +- mt7530_core_reg_write(priv, CORE_GSWPLL_GRP1, ++ mt753x_core_reg_write(priv, CORE_GSWPLL_GRP1, + (2 << RG_GSWPLL_POSDIV_200M_S) | + (32 << RG_GSWPLL_FBKDIV_200M_S) | + RG_GSWPLL_EN_PRE); + + udelay(20); + +- mt7530_core_reg_write(priv, CORE_TRGMII_GSW_CLK_CG, REG_GSWCK_EN); ++ mt753x_core_reg_write(priv, CORE_TRGMII_GSW_CLK_CG, REG_GSWCK_EN); + + /* Setup the MT7530 TRGMII Tx Clock */ +- mt7530_core_reg_write(priv, CORE_PLL_GROUP5, ncpo1); +- mt7530_core_reg_write(priv, CORE_PLL_GROUP6, 0); +- mt7530_core_reg_write(priv, CORE_PLL_GROUP10, ssc_delta); +- mt7530_core_reg_write(priv, CORE_PLL_GROUP11, ssc_delta); +- mt7530_core_reg_write(priv, CORE_PLL_GROUP4, RG_SYSPLL_DDSFBK_EN | ++ mt753x_core_reg_write(priv, CORE_PLL_GROUP5, ncpo1); ++ mt753x_core_reg_write(priv, CORE_PLL_GROUP6, 0); ++ mt753x_core_reg_write(priv, CORE_PLL_GROUP10, ssc_delta); ++ mt753x_core_reg_write(priv, CORE_PLL_GROUP11, ssc_delta); ++ mt753x_core_reg_write(priv, CORE_PLL_GROUP4, RG_SYSPLL_DDSFBK_EN | + RG_SYSPLL_BIAS_EN | RG_SYSPLL_BIAS_LPF_EN); + +- mt7530_core_reg_write(priv, CORE_PLL_GROUP2, ++ mt753x_core_reg_write(priv, CORE_PLL_GROUP2, + RG_SYSPLL_EN_NORMAL | RG_SYSPLL_VODEN | + (1 << RG_SYSPLL_POSDIV_S)); + +- mt7530_core_reg_write(priv, CORE_PLL_GROUP7, ++ mt753x_core_reg_write(priv, CORE_PLL_GROUP7, + RG_LCDDS_PCW_NCPO_CHG | (3 << RG_LCCDS_C_S) | + RG_LCDDS_PWDB | RG_LCDDS_ISO_EN); + + /* Enable MT7530 core clock */ +- mt7530_core_reg_write(priv, CORE_TRGMII_GSW_CLK_CG, ++ mt753x_core_reg_write(priv, CORE_TRGMII_GSW_CLK_CG, + REG_GSWCK_EN | REG_TRGMIICK_EN); + + return 0; +@@ -551,46 +671,33 @@ static int mt7530_setup(struct mtk_eth_priv *priv) + mtk_ethsys_rmw(priv, ETHSYS_CLKCFG0_REG, + ETHSYS_TRGMII_CLK_SEL362_5, 0); + +- /* Global reset switch */ +- if (priv->mcm) { +- reset_assert(&priv->rst_mcm); +- udelay(1000); +- reset_deassert(&priv->rst_mcm); +- mdelay(1000); +- } else if (dm_gpio_is_valid(&priv->rst_gpio)) { +- dm_gpio_set_value(&priv->rst_gpio, 0); +- udelay(1000); +- dm_gpio_set_value(&priv->rst_gpio, 1); +- mdelay(1000); +- } +- + /* Modify HWTRAP first to allow direct access to internal PHYs */ +- mt7530_reg_read(priv, HWTRAP_REG, &val); ++ mt753x_reg_read(priv, HWTRAP_REG, &val); + val |= CHG_TRAP; + val &= ~C_MDIO_BPS; +- mt7530_reg_write(priv, MHWTRAP_REG, val); ++ mt753x_reg_write(priv, MHWTRAP_REG, val); + + /* Calculate the phy base address */ + val = ((val & SMI_ADDR_M) >> SMI_ADDR_S) << 3; +- priv->mt7530_phy_base = (val | 0x7) + 1; ++ priv->mt753x_phy_base = (val | 0x7) + 1; + + /* Turn off PHYs */ +- for (i = 0; i < MT7530_NUM_PHYS; i++) { +- phy_addr = MT7530_PHY_ADDR(priv->mt7530_phy_base, i); ++ for (i = 0; i < MT753X_NUM_PHYS; i++) { ++ phy_addr = MT753X_PHY_ADDR(priv->mt753x_phy_base, i); + phy_val = priv->mii_read(priv, phy_addr, MII_BMCR); + phy_val |= BMCR_PDOWN; + priv->mii_write(priv, phy_addr, MII_BMCR, phy_val); + } + + /* Force MAC link down before reset */ +- mt7530_reg_write(priv, PCMR_REG(5), FORCE_MODE); +- mt7530_reg_write(priv, PCMR_REG(6), FORCE_MODE); ++ mt753x_reg_write(priv, PMCR_REG(5), FORCE_MODE); ++ mt753x_reg_write(priv, PMCR_REG(6), FORCE_MODE); + + /* MT7530 reset */ +- mt7530_reg_write(priv, SYS_CTRL_REG, SW_SYS_RST | SW_REG_RST); ++ mt753x_reg_write(priv, SYS_CTRL_REG, SW_SYS_RST | SW_REG_RST); + udelay(100); + +- val = (1 << IPG_CFG_S) | ++ val = (IPG_96BIT_WITH_SHORT_IPG << IPG_CFG_S) | + MAC_MODE | FORCE_MODE | + MAC_TX_EN | MAC_RX_EN | + BKOFF_EN | BACKPR_EN | +@@ -598,53 +705,280 @@ static int mt7530_setup(struct mtk_eth_priv *priv) + FORCE_DPX | FORCE_LINK; + + /* MT7530 Port6: Forced 1000M/FD, FC disabled */ +- mt7530_reg_write(priv, PCMR_REG(6), val); ++ mt753x_reg_write(priv, PMCR_REG(6), val); + + /* MT7530 Port5: Forced link down */ +- mt7530_reg_write(priv, PCMR_REG(5), FORCE_MODE); ++ mt753x_reg_write(priv, PMCR_REG(5), FORCE_MODE); + + /* MT7530 Port6: Set to RGMII */ +- mt7530_reg_rmw(priv, MT7530_P6ECR, P6_INTF_MODE_M, P6_INTF_MODE_RGMII); ++ mt753x_reg_rmw(priv, MT7530_P6ECR, P6_INTF_MODE_M, P6_INTF_MODE_RGMII); + + /* Hardware Trap: Enable Port6, Disable Port5 */ +- mt7530_reg_read(priv, HWTRAP_REG, &val); ++ mt753x_reg_read(priv, HWTRAP_REG, &val); + val |= CHG_TRAP | LOOPDET_DIS | P5_INTF_DIS | + (P5_INTF_SEL_GMAC5 << P5_INTF_SEL_S) | + (P5_INTF_MODE_RGMII << P5_INTF_MODE_S); + val &= ~(C_MDIO_BPS | P6_INTF_DIS); +- mt7530_reg_write(priv, MHWTRAP_REG, val); ++ mt753x_reg_write(priv, MHWTRAP_REG, val); + + /* Setup switch core pll */ + mt7530_pad_clk_setup(priv, priv->phy_interface); + + /* Lower Tx Driving for TRGMII path */ + for (i = 0 ; i < NUM_TRGMII_CTRL ; i++) +- mt7530_reg_write(priv, MT7530_TRGMII_TD_ODT(i), ++ mt753x_reg_write(priv, MT7530_TRGMII_TD_ODT(i), + (8 << TD_DM_DRVP_S) | (8 << TD_DM_DRVN_S)); + + for (i = 0 ; i < NUM_TRGMII_CTRL; i++) +- mt7530_reg_rmw(priv, MT7530_TRGMII_RD(i), RD_TAP_M, 16); ++ mt753x_reg_rmw(priv, MT7530_TRGMII_RD(i), RD_TAP_M, 16); ++ ++ /* Turn on PHYs */ ++ for (i = 0; i < MT753X_NUM_PHYS; i++) { ++ phy_addr = MT753X_PHY_ADDR(priv->mt753x_phy_base, i); ++ phy_val = priv->mii_read(priv, phy_addr, MII_BMCR); ++ phy_val &= ~BMCR_PDOWN; ++ priv->mii_write(priv, phy_addr, MII_BMCR, phy_val); ++ } ++ ++ return 0; ++} ++ ++static void mt7531_core_pll_setup(struct mtk_eth_priv *priv, int mcm) ++{ ++ /* Step 1 : Disable MT7531 COREPLL */ ++ mt753x_reg_rmw(priv, MT7531_PLLGP_EN, EN_COREPLL, 0); ++ ++ /* Step 2: switch to XTAL output */ ++ mt753x_reg_rmw(priv, MT7531_PLLGP_EN, SW_CLKSW, SW_CLKSW); ++ ++ mt753x_reg_rmw(priv, MT7531_PLLGP_CR0, RG_COREPLL_EN, 0); ++ ++ /* Step 3: disable PLLGP and enable program PLLGP */ ++ mt753x_reg_rmw(priv, MT7531_PLLGP_EN, SW_PLLGP, SW_PLLGP); ++ ++ /* Step 4: program COREPLL output frequency to 500MHz */ ++ mt753x_reg_rmw(priv, MT7531_PLLGP_CR0, RG_COREPLL_POSDIV_M, ++ 2 << RG_COREPLL_POSDIV_S); ++ udelay(25); ++ ++ /* Currently, support XTAL 25Mhz only */ ++ mt753x_reg_rmw(priv, MT7531_PLLGP_CR0, RG_COREPLL_SDM_PCW_M, ++ 0x140000 << RG_COREPLL_SDM_PCW_S); ++ ++ /* Set feedback divide ratio update signal to high */ ++ mt753x_reg_rmw(priv, MT7531_PLLGP_CR0, RG_COREPLL_SDM_PCW_CHG, ++ RG_COREPLL_SDM_PCW_CHG); ++ ++ /* Wait for at least 16 XTAL clocks */ ++ udelay(10); ++ ++ /* Step 5: set feedback divide ratio update signal to low */ ++ mt753x_reg_rmw(priv, MT7531_PLLGP_CR0, RG_COREPLL_SDM_PCW_CHG, 0); ++ ++ /* add enable 325M clock for SGMII */ ++ mt753x_reg_write(priv, MT7531_ANA_PLLGP_CR5, 0xad0000); ++ ++ /* add enable 250SSC clock for RGMII */ ++ mt753x_reg_write(priv, MT7531_ANA_PLLGP_CR2, 0x4f40000); ++ ++ /*Step 6: Enable MT7531 PLL */ ++ mt753x_reg_rmw(priv, MT7531_PLLGP_CR0, RG_COREPLL_EN, RG_COREPLL_EN); ++ ++ mt753x_reg_rmw(priv, MT7531_PLLGP_EN, EN_COREPLL, EN_COREPLL); ++ ++ udelay(25); ++} ++ ++static int mt7531_port_sgmii_init(struct mtk_eth_priv *priv, ++ u32 port) ++{ ++ if (port != 5 && port != 6) { ++ printf("mt7531: port %d is not a SGMII port\n", port); ++ return -EINVAL; ++ } ++ ++ /* Set SGMII GEN2 speed(2.5G) */ ++ mt753x_reg_rmw(priv, MT7531_PHYA_CTRL_SIGNAL3(port), ++ SGMSYS_SPEED_2500, SGMSYS_SPEED_2500); ++ ++ /* Disable SGMII AN */ ++ mt753x_reg_rmw(priv, MT7531_PCS_CONTROL_1(port), ++ SGMII_AN_ENABLE, 0); ++ ++ /* SGMII force mode setting */ ++ mt753x_reg_write(priv, MT7531_SGMII_MODE(port), SGMII_FORCE_MODE); ++ ++ /* Release PHYA power down state */ ++ mt753x_reg_rmw(priv, MT7531_QPHY_PWR_STATE_CTRL(port), ++ SGMII_PHYA_PWD, 0); ++ ++ return 0; ++} ++ ++static int mt7531_port_rgmii_init(struct mtk_eth_priv *priv, u32 port) ++{ ++ u32 val; ++ ++ if (port != 5) { ++ printf("error: RGMII mode is not available for port %d\n", ++ port); ++ return -EINVAL; ++ } ++ ++ mt753x_reg_read(priv, MT7531_CLKGEN_CTRL, &val); ++ val |= GP_CLK_EN; ++ val &= ~GP_MODE_M; ++ val |= GP_MODE_RGMII << GP_MODE_S; ++ val |= TXCLK_NO_REVERSE; ++ val |= RXCLK_NO_DELAY; ++ val &= ~CLK_SKEW_IN_M; ++ val |= CLK_SKEW_IN_NO_CHANGE << CLK_SKEW_IN_S; ++ val &= ~CLK_SKEW_OUT_M; ++ val |= CLK_SKEW_OUT_NO_CHANGE << CLK_SKEW_OUT_S; ++ mt753x_reg_write(priv, MT7531_CLKGEN_CTRL, val); ++ ++ return 0; ++} ++ ++static void mt7531_phy_setting(struct mtk_eth_priv *priv) ++{ ++ int i; ++ u32 val; ++ ++ for (i = 0; i < MT753X_NUM_PHYS; i++) { ++ /* Enable HW auto downshift */ ++ priv->mii_write(priv, i, 0x1f, 0x1); ++ val = priv->mii_read(priv, i, PHY_EXT_REG_14); ++ val |= PHY_EN_DOWN_SHFIT; ++ priv->mii_write(priv, i, PHY_EXT_REG_14, val); ++ ++ /* PHY link down power saving enable */ ++ val = priv->mii_read(priv, i, PHY_EXT_REG_17); ++ val |= PHY_LINKDOWN_POWER_SAVING_EN; ++ priv->mii_write(priv, i, PHY_EXT_REG_17, val); ++ ++ val = priv->mmd_read(priv, i, 0x1e, PHY_DEV1E_REG_0C6); ++ val &= ~PHY_POWER_SAVING_M; ++ val |= PHY_POWER_SAVING_TX << PHY_POWER_SAVING_S; ++ priv->mmd_write(priv, i, 0x1e, PHY_DEV1E_REG_0C6, val); ++ } ++} ++ ++static int mt7531_setup(struct mtk_eth_priv *priv) ++{ ++ u16 phy_addr, phy_val; ++ u32 val; ++ u32 pmcr; ++ u32 port5_sgmii; ++ int i; ++ ++ priv->mt753x_phy_base = (priv->mt753x_smi_addr + 1) & ++ MT753X_SMI_ADDR_MASK; ++ ++ /* Turn off PHYs */ ++ for (i = 0; i < MT753X_NUM_PHYS; i++) { ++ phy_addr = MT753X_PHY_ADDR(priv->mt753x_phy_base, i); ++ phy_val = priv->mii_read(priv, phy_addr, MII_BMCR); ++ phy_val |= BMCR_PDOWN; ++ priv->mii_write(priv, phy_addr, MII_BMCR, phy_val); ++ } ++ ++ /* Force MAC link down before reset */ ++ mt753x_reg_write(priv, PMCR_REG(5), FORCE_MODE_LNK); ++ mt753x_reg_write(priv, PMCR_REG(6), FORCE_MODE_LNK); ++ ++ /* Switch soft reset */ ++ mt753x_reg_write(priv, SYS_CTRL_REG, SW_SYS_RST | SW_REG_RST); ++ udelay(100); ++ ++ /* Enable MDC input Schmitt Trigger */ ++ mt753x_reg_rmw(priv, MT7531_SMT0_IOLB, SMT_IOLB_5_SMI_MDC_EN, ++ SMT_IOLB_5_SMI_MDC_EN); ++ ++ mt7531_core_pll_setup(priv, priv->mcm); ++ ++ mt753x_reg_read(priv, MT7531_TOP_SIG_SR, &val); ++ port5_sgmii = !!(val & PAD_DUAL_SGMII_EN); ++ ++ /* port5 support either RGMII or SGMII, port6 only support SGMII. */ ++ switch (priv->phy_interface) { ++ case PHY_INTERFACE_MODE_RGMII: ++ if (!port5_sgmii) ++ mt7531_port_rgmii_init(priv, 5); ++ break; ++ case PHY_INTERFACE_MODE_SGMII: ++ mt7531_port_sgmii_init(priv, 6); ++ if (port5_sgmii) ++ mt7531_port_sgmii_init(priv, 5); ++ break; ++ default: ++ break; ++ } ++ ++ pmcr = MT7531_FORCE_MODE | ++ (IPG_96BIT_WITH_SHORT_IPG << IPG_CFG_S) | ++ MAC_MODE | MAC_TX_EN | MAC_RX_EN | ++ BKOFF_EN | BACKPR_EN | ++ FORCE_RX_FC | FORCE_TX_FC | ++ (SPEED_1000M << FORCE_SPD_S) | FORCE_DPX | ++ FORCE_LINK; ++ ++ mt753x_reg_write(priv, PMCR_REG(5), pmcr); ++ mt753x_reg_write(priv, PMCR_REG(6), pmcr); + + /* Turn on PHYs */ +- for (i = 0; i < MT7530_NUM_PHYS; i++) { +- phy_addr = MT7530_PHY_ADDR(priv->mt7530_phy_base, i); ++ for (i = 0; i < MT753X_NUM_PHYS; i++) { ++ phy_addr = MT753X_PHY_ADDR(priv->mt753x_phy_base, i); + phy_val = priv->mii_read(priv, phy_addr, MII_BMCR); + phy_val &= ~BMCR_PDOWN; + priv->mii_write(priv, phy_addr, MII_BMCR, phy_val); + } + ++ mt7531_phy_setting(priv); ++ ++ /* Enable Internal PHYs */ ++ val = mt753x_core_reg_read(priv, CORE_PLL_GROUP4); ++ val |= MT7531_BYPASS_MODE; ++ val &= ~MT7531_POWER_ON_OFF; ++ mt753x_core_reg_write(priv, CORE_PLL_GROUP4, val); ++ ++ return 0; ++} ++ ++int mt753x_switch_init(struct mtk_eth_priv *priv) ++{ ++ int ret; ++ int i; ++ ++ /* Global reset switch */ ++ if (priv->mcm) { ++ reset_assert(&priv->rst_mcm); ++ udelay(1000); ++ reset_deassert(&priv->rst_mcm); ++ mdelay(1000); ++ } else if (dm_gpio_is_valid(&priv->rst_gpio)) { ++ dm_gpio_set_value(&priv->rst_gpio, 0); ++ udelay(1000); ++ dm_gpio_set_value(&priv->rst_gpio, 1); ++ mdelay(1000); ++ } ++ ++ ret = priv->switch_init(priv); ++ if (ret) ++ return ret; ++ + /* Set port isolation */ +- for (i = 0; i < 8; i++) { ++ for (i = 0; i < MT753X_NUM_PORTS; i++) { + /* Set port matrix mode */ + if (i != 6) +- mt7530_reg_write(priv, PCR_REG(i), ++ mt753x_reg_write(priv, PCR_REG(i), + (0x40 << PORT_MATRIX_S)); + else +- mt7530_reg_write(priv, PCR_REG(i), ++ mt753x_reg_write(priv, PCR_REG(i), + (0x3f << PORT_MATRIX_S)); + + /* Set port mode to user port */ +- mt7530_reg_write(priv, PVC_REG(i), ++ mt753x_reg_write(priv, PVC_REG(i), + (0x8100 << STAG_VPID_S) | + (VLAN_ATTR_USER << VLAN_ATTR_S)); + } +@@ -658,7 +992,7 @@ static void mtk_phy_link_adjust(struct mtk_eth_priv *priv) + u8 flowctrl; + u32 mcr; + +- mcr = (1 << IPG_CFG_S) | ++ mcr = (IPG_96BIT_WITH_SHORT_IPG << IPG_CFG_S) | + (MAC_RX_PKT_LEN_1536 << MAC_RX_PKT_LEN_S) | + MAC_MODE | FORCE_MODE | + MAC_TX_EN | MAC_RX_EN | +@@ -803,7 +1137,7 @@ static void mtk_mac_init(struct mtk_eth_priv *priv) + ge_mode << SYSCFG0_GE_MODE_S(priv->gmac_id)); + + if (priv->force_mode) { +- mcr = (1 << IPG_CFG_S) | ++ mcr = (IPG_96BIT_WITH_SHORT_IPG << IPG_CFG_S) | + (MAC_RX_PKT_LEN_1536 << MAC_RX_PKT_LEN_S) | + MAC_MODE | FORCE_MODE | + MAC_TX_EN | MAC_RX_EN | +@@ -1050,7 +1384,7 @@ static int mtk_eth_probe(struct udevice *dev) + return mtk_phy_probe(dev); + + /* Initialize switch */ +- return priv->switch_init(priv); ++ return mt753x_switch_init(priv); + } + + static int mtk_eth_remove(struct udevice *dev) +@@ -1159,7 +1493,11 @@ static int mtk_eth_ofdata_to_platdata(struct udevice *dev) + if (!strcmp(str, "mt7530")) { + priv->sw = SW_MT7530; + priv->switch_init = mt7530_setup; +- priv->mt7530_smi_addr = MT7530_DFL_SMI_ADDR; ++ priv->mt753x_smi_addr = MT753X_DFL_SMI_ADDR; ++ } else if (!strcmp(str, "mt7531")) { ++ priv->sw = SW_MT7531; ++ priv->switch_init = mt7531_setup; ++ priv->mt753x_smi_addr = MT753X_DFL_SMI_ADDR; + } else { + printf("error: unsupported switch\n"); + return -EINVAL; +diff --git a/drivers/net/mtk_eth.h b/drivers/net/mtk_eth.h +index 9bb037d440..f2940c9996 100644 +--- a/drivers/net/mtk_eth.h ++++ b/drivers/net/mtk_eth.h +@@ -34,7 +34,9 @@ + + /* SGMII subsystem config registers */ + #define SGMSYS_PCS_CONTROL_1 0x0 ++#define SGMII_LINK_STATUS BIT(18) + #define SGMII_AN_ENABLE BIT(12) ++#define SGMII_AN_RESTART BIT(9) + + #define SGMSYS_SGMII_MODE 0x20 + #define SGMII_FORCE_MODE 0x31120019 +@@ -139,6 +141,11 @@ + #define FORCE_DPX BIT(1) + #define FORCE_LINK BIT(0) + ++/* Values of IPG_CFG */ ++#define IPG_96BIT 0 ++#define IPG_96BIT_WITH_SHORT_IPG 1 ++#define IPG_64BIT 2 ++ + /* MAC_RX_PKT_LEN: Max RX packet length */ + #define MAC_RX_PKT_LEN_1518 0 + #define MAC_RX_PKT_LEN_1536 1 +@@ -178,17 +185,73 @@ + #define VLAN_ATTR_TRANSLATION 2 + #define VLAN_ATTR_TRANSPARENT 3 + +-#define PCMR_REG(p) (0x3000 + (p) * 0x100) +-/* XXX: all fields are defined under GMAC_PORT_MCR */ +- ++#define PMCR_REG(p) (0x3000 + (p) * 0x100) ++/* XXX: all fields of MT7530 are defined under GMAC_PORT_MCR ++ * MT7531 specific fields are defined below ++ */ ++#define FORCE_MODE_EEE1G BIT(25) ++#define FORCE_MODE_EEE100 BIT(26) ++#define FORCE_MODE_TX_FC BIT(27) ++#define FORCE_MODE_RX_FC BIT(28) ++#define FORCE_MODE_DPX BIT(29) ++#define FORCE_MODE_SPD BIT(30) ++#define FORCE_MODE_LNK BIT(31) ++#define MT7531_FORCE_MODE FORCE_MODE_EEE1G | FORCE_MODE_EEE100 |\ ++ FORCE_MODE_TX_FC | FORCE_MODE_RX_FC | \ ++ FORCE_MODE_DPX | FORCE_MODE_SPD | \ ++ FORCE_MODE_LNK ++ ++/* MT7531 SGMII Registers */ ++#define MT7531_SGMII_REG_BASE 0x5000 ++#define MT7531_SGMII_REG_PORT_BASE 0x1000 ++#define MT7531_SGMII_REG(p, r) (MT7531_SGMII_REG_BASE + \ ++ (p) * MT7531_SGMII_REG_PORT_BASE + (r)) ++#define MT7531_PCS_CONTROL_1(p) MT7531_SGMII_REG(((p) - 5), 0x00) ++#define MT7531_SGMII_MODE(p) MT7531_SGMII_REG(((p) - 5), 0x20) ++#define MT7531_QPHY_PWR_STATE_CTRL(p) MT7531_SGMII_REG(((p) - 5), 0xe8) ++#define MT7531_PHYA_CTRL_SIGNAL3(p) MT7531_SGMII_REG(((p) - 5), 0x128) ++/* XXX: all fields of MT7531 SGMII are defined under SGMSYS */ ++ ++/* MT753x System Control Register */ + #define SYS_CTRL_REG 0x7000 + #define SW_PHY_RST BIT(2) + #define SW_SYS_RST BIT(1) + #define SW_REG_RST BIT(0) + +-#define NUM_TRGMII_CTRL 5 ++/* MT7531 */ ++#define MT7531_PHY_IAC 0x701c ++/* XXX: all fields are defined under GMAC_PIAC_REG */ ++ ++#define MT7531_CLKGEN_CTRL 0x7500 ++#define CLK_SKEW_OUT_S 8 ++#define CLK_SKEW_OUT_M 0x300 ++#define CLK_SKEW_IN_S 6 ++#define CLK_SKEW_IN_M 0xc0 ++#define RXCLK_NO_DELAY BIT(5) ++#define TXCLK_NO_REVERSE BIT(4) ++#define GP_MODE_S 1 ++#define GP_MODE_M 0x06 ++#define GP_CLK_EN BIT(0) ++ ++/* Values of GP_MODE */ ++#define GP_MODE_RGMII 0 ++#define GP_MODE_MII 1 ++#define GP_MODE_REV_MII 2 ++ ++/* Values of CLK_SKEW_IN */ ++#define CLK_SKEW_IN_NO_CHANGE 0 ++#define CLK_SKEW_IN_DELAY_100PPS 1 ++#define CLK_SKEW_IN_DELAY_200PPS 2 ++#define CLK_SKEW_IN_REVERSE 3 ++ ++/* Values of CLK_SKEW_OUT */ ++#define CLK_SKEW_OUT_NO_CHANGE 0 ++#define CLK_SKEW_OUT_DELAY_100PPS 1 ++#define CLK_SKEW_OUT_DELAY_200PPS 2 ++#define CLK_SKEW_OUT_REVERSE 3 + + #define HWTRAP_REG 0x7800 ++/* MT7530 Modified Hardware Trap Status Registers */ + #define MHWTRAP_REG 0x7804 + #define CHG_TRAP BIT(16) + #define LOOPDET_DIS BIT(14) +@@ -222,6 +285,8 @@ + #define P6_INTF_MODE_RGMII 0 + #define P6_INTF_MODE_TRGMII 1 + ++#define NUM_TRGMII_CTRL 5 ++ + #define MT7530_TRGMII_RD(n) (0x7a10 + (n) * 8) + #define RD_TAP_S 0 + #define RD_TAP_M 0x7f +@@ -229,8 +294,34 @@ + #define MT7530_TRGMII_TD_ODT(n) (0x7a54 + (n) * 8) + /* XXX: all fields are defined under GMAC_TRGMII_TD_ODT */ + +-/* MT7530 GPHY MDIO Indirect Access Registers */ ++/* TOP Signals Status Register */ ++#define MT7531_TOP_SIG_SR 0x780c ++#define PAD_MCM_SMI_EN BIT(0) ++#define PAD_DUAL_SGMII_EN BIT(1) ++ ++/* MT7531 PLLGP Registers */ ++#define MT7531_PLLGP_EN 0x7820 ++#define EN_COREPLL BIT(2) ++#define SW_CLKSW BIT(1) ++#define SW_PLLGP BIT(0) ++ ++#define MT7531_PLLGP_CR0 0x78a8 ++#define RG_COREPLL_EN BIT(22) ++#define RG_COREPLL_POSDIV_S 23 ++#define RG_COREPLL_POSDIV_M 0x3800000 ++#define RG_COREPLL_SDM_PCW_S 1 ++#define RG_COREPLL_SDM_PCW_M 0x3ffffe ++#define RG_COREPLL_SDM_PCW_CHG BIT(0) ++ ++/* MT7531 RGMII and SGMII PLL clock */ ++#define MT7531_ANA_PLLGP_CR2 0x78b0 ++#define MT7531_ANA_PLLGP_CR5 0x78bc ++ ++/* MT7531 GPIO GROUP IOLB SMT0 Control */ ++#define MT7531_SMT0_IOLB 0x7f04 ++#define SMT_IOLB_5_SMI_MDC_EN BIT(5) + ++/* MT7530 GPHY MDIO Indirect Access Registers */ + #define MII_MMD_ACC_CTL_REG 0x0d + #define MMD_CMD_S 14 + #define MMD_CMD_M 0xc000 +@@ -246,7 +337,6 @@ + #define MII_MMD_ADDR_DATA_REG 0x0e + + /* MT7530 GPHY MDIO MMD Registers */ +- + #define CORE_PLL_GROUP2 0x401 + #define RG_SYSPLL_EN_NORMAL BIT(15) + #define RG_SYSPLL_VODEN BIT(14) +@@ -254,6 +344,8 @@ + #define RG_SYSPLL_POSDIV_M 0x60 + + #define CORE_PLL_GROUP4 0x403 ++#define MT7531_BYPASS_MODE BIT(4) ++#define MT7531_POWER_ON_OFF BIT(5) + #define RG_SYSPLL_DDSFBK_EN BIT(12) + #define RG_SYSPLL_BIAS_EN BIT(11) + #define RG_SYSPLL_BIAS_LPF_EN BIT(10) +@@ -298,4 +390,24 @@ + #define REG_GSWCK_EN BIT(0) + #define REG_TRGMIICK_EN BIT(1) + ++/* Extend PHY Control Register 3 */ ++#define PHY_EXT_REG_14 0x14 ++ ++/* Fields of PHY_EXT_REG_14 */ ++#define PHY_EN_DOWN_SHFIT BIT(4) ++ ++/* Extend PHY Control Register 4 */ ++#define PHY_EXT_REG_17 0x17 ++ ++/* Fields of PHY_EXT_REG_17 */ ++#define PHY_LINKDOWN_POWER_SAVING_EN BIT(4) ++ ++/* PHY RXADC Control Register 7 */ ++#define PHY_DEV1E_REG_0C6 0x0c6 ++ ++/* Fields of PHY_DEV1E_REG_0C6 */ ++#define PHY_POWER_SAVING_S 8 ++#define PHY_POWER_SAVING_M 0x300 ++#define PHY_POWER_SAVING_TX 0x0 ++ + #endif /* _MTK_ETH_H_ */ diff --git a/package/boot/uboot-mediatek/patches/002-nand-add-spi-nand-driver.patch b/package/boot/uboot-mediatek/patches/002-nand-add-spi-nand-driver.patch new file mode 100644 index 0000000000..dc3ebaf7af --- /dev/null +++ b/package/boot/uboot-mediatek/patches/002-nand-add-spi-nand-driver.patch @@ -0,0 +1,8659 @@ +From de8b6cf615be20b25d0f3c817866de2c0d46a704 Mon Sep 17 00:00:00 2001 +From: Sam Shih +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 +--- + 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 + +diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig +index 5e7571cf3d..34a59b44b9 100644 +--- a/drivers/mtd/Kconfig ++++ b/drivers/mtd/Kconfig +@@ -101,6 +101,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" +diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile +index 318788c5e2..1df1031b23 100644 +--- a/drivers/mtd/Makefile ++++ b/drivers/mtd/Makefile +@@ -41,3 +41,7 @@ obj-$(CONFIG_$(SPL_TPL_)SPI_FLASH_SUPPORT) += spi/ + obj-$(CONFIG_SPL_UBI) += ubispl/ + + endif ++ ++ifeq ($(CONFIG_MTK_SPI_NAND), y) ++include $(srctree)/drivers/mtd/nandx/Nandx.mk ++endif +diff --git a/drivers/mtd/nand/raw/nand.c b/drivers/mtd/nand/raw/nand.c +index 026419e4e6..4be0c7d8f3 100644 +--- 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); + } +diff --git a/drivers/mtd/nandx/NOTICE b/drivers/mtd/nandx/NOTICE +new file mode 100644 +index 0000000000..1a06ca3867 +--- /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 +diff --git a/drivers/mtd/nandx/Nandx.config b/drivers/mtd/nandx/Nandx.config +new file mode 100644 +index 0000000000..35705ee28d +--- /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 +diff --git a/drivers/mtd/nandx/Nandx.mk b/drivers/mtd/nandx/Nandx.mk +new file mode 100644 +index 0000000000..f5a6f2a628 +--- /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 +diff --git a/drivers/mtd/nandx/README b/drivers/mtd/nandx/README +new file mode 100644 +index 0000000000..0feaeaeb88 +--- /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. +diff --git a/drivers/mtd/nandx/core/Nandx.mk b/drivers/mtd/nandx/core/Nandx.mk +new file mode 100644 +index 0000000000..7a5661c044 +--- /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 +diff --git a/drivers/mtd/nandx/core/core_io.c b/drivers/mtd/nandx/core/core_io.c +new file mode 100644 +index 0000000000..716eeed38d +--- /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 +diff --git a/drivers/mtd/nandx/core/core_io.h b/drivers/mtd/nandx/core/core_io.h +new file mode 100644 +index 0000000000..edcb60908a +--- /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__ */ +diff --git a/drivers/mtd/nandx/core/nand/device_spi.c b/drivers/mtd/nandx/core/nand/device_spi.c +new file mode 100644 +index 0000000000..db338c28c2 +--- /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; ++} ++ +diff --git a/drivers/mtd/nandx/core/nand/device_spi.h b/drivers/mtd/nandx/core/nand/device_spi.h +new file mode 100644 +index 0000000000..1676b61fc8 +--- /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__ */ +diff --git a/drivers/mtd/nandx/core/nand/nand_spi.c b/drivers/mtd/nandx/core/nand/nand_spi.c +new file mode 100644 +index 0000000000..2ae03e1cf4 +--- /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, ¶m, 1); ++ param |= die_sel; ++ nand->set_feature(nand, dev->feature.character.addr, ¶m, 1); ++ param = 0; ++ nand->get_feature(nand, dev->feature.character.addr, ¶m, 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, ¶m, 1); ++ param |= BIT(dev->feature.config.otp_en_bit); ++ nand->set_feature(nand, dev->feature.config.addr, ¶m, 1); ++ ++ param = 0; ++ nand->get_feature(nand, dev->feature.config.addr, ¶m, 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, ¶m, 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); ++} +diff --git a/drivers/mtd/nandx/core/nand/nand_spi.h b/drivers/mtd/nandx/core/nand/nand_spi.h +new file mode 100644 +index 0000000000..e55e4de6f7 +--- /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__ */ +diff --git a/drivers/mtd/nandx/core/nand_base.c b/drivers/mtd/nandx/core/nand_base.c +new file mode 100644 +index 0000000000..65998e5460 +--- /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); ++} +diff --git a/drivers/mtd/nandx/core/nand_base.h b/drivers/mtd/nandx/core/nand_base.h +new file mode 100644 +index 0000000000..13217978e5 +--- /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__ */ +diff --git a/drivers/mtd/nandx/core/nand_chip.c b/drivers/mtd/nandx/core/nand_chip.c +new file mode 100644 +index 0000000000..02adc6f52e +--- /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); ++} +diff --git a/drivers/mtd/nandx/core/nand_chip.h b/drivers/mtd/nandx/core/nand_chip.h +new file mode 100644 +index 0000000000..3e9c8e6ca3 +--- /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__ */ +diff --git a/drivers/mtd/nandx/core/nand_device.c b/drivers/mtd/nandx/core/nand_device.c +new file mode 100644 +index 0000000000..9f6764d1bc +--- /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; ++} ++ +diff --git a/drivers/mtd/nandx/core/nand_device.h b/drivers/mtd/nandx/core/nand_device.h +new file mode 100644 +index 0000000000..e142cf529d +--- /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__ */ +diff --git a/drivers/mtd/nandx/core/nfi.h b/drivers/mtd/nandx/core/nfi.h +new file mode 100644 +index 0000000000..ba84e73ccc +--- /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__ */ +diff --git a/drivers/mtd/nandx/core/nfi/nfi_base.c b/drivers/mtd/nandx/core/nfi/nfi_base.c +new file mode 100644 +index 0000000000..d8679d7aa3 +--- /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); ++} ++ +diff --git a/drivers/mtd/nandx/core/nfi/nfi_base.h b/drivers/mtd/nandx/core/nfi/nfi_base.h +new file mode 100644 +index 0000000000..ae894eaa31 +--- /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__ */ +diff --git a/drivers/mtd/nandx/core/nfi/nfi_regs.h b/drivers/mtd/nandx/core/nfi/nfi_regs.h +new file mode 100644 +index 0000000000..ba4868acc8 +--- /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__ */ ++ +diff --git a/drivers/mtd/nandx/core/nfi/nfi_spi.c b/drivers/mtd/nandx/core/nfi/nfi_spi.c +new file mode 100644 +index 0000000000..67cd0aaad9 +--- /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); ++} ++ +diff --git a/drivers/mtd/nandx/core/nfi/nfi_spi.h b/drivers/mtd/nandx/core/nfi/nfi_spi.h +new file mode 100644 +index 0000000000..a52255663a +--- /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__ */ +diff --git a/drivers/mtd/nandx/core/nfi/nfi_spi_regs.h b/drivers/mtd/nandx/core/nfi/nfi_spi_regs.h +new file mode 100644 +index 0000000000..77adf46782 +--- /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__ */ +diff --git a/drivers/mtd/nandx/core/nfi/nfiecc.c b/drivers/mtd/nandx/core/nfi/nfiecc.c +new file mode 100644 +index 0000000000..14246fbc3e +--- /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); ++} ++ +diff --git a/drivers/mtd/nandx/core/nfi/nfiecc.h b/drivers/mtd/nandx/core/nfi/nfiecc.h +new file mode 100644 +index 0000000000..b02a5c3534 +--- /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__ */ +diff --git a/drivers/mtd/nandx/core/nfi/nfiecc_regs.h b/drivers/mtd/nandx/core/nfi/nfiecc_regs.h +new file mode 100644 +index 0000000000..96564cf872 +--- /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__ */ +diff --git a/drivers/mtd/nandx/driver/Nandx.mk b/drivers/mtd/nandx/driver/Nandx.mk +new file mode 100644 +index 0000000000..3fb93d37c5 +--- /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 +diff --git a/drivers/mtd/nandx/driver/bbt/bbt.c b/drivers/mtd/nandx/driver/bbt/bbt.c +new file mode 100644 +index 0000000000..c9d4823e09 +--- /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; ++} +diff --git a/drivers/mtd/nandx/driver/uboot/driver.c b/drivers/mtd/nandx/driver/uboot/driver.c +new file mode 100644 +index 0000000000..7bd3342452 +--- /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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#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"); +diff --git a/drivers/mtd/nandx/include/Nandx.mk b/drivers/mtd/nandx/include/Nandx.mk +new file mode 100644 +index 0000000000..667402790e +--- /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 +diff --git a/drivers/mtd/nandx/include/internal/bbt.h b/drivers/mtd/nandx/include/internal/bbt.h +new file mode 100644 +index 0000000000..4676def1f5 +--- /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__*/ +diff --git a/drivers/mtd/nandx/include/internal/nandx_core.h b/drivers/mtd/nandx/include/internal/nandx_core.h +new file mode 100644 +index 0000000000..09aff72224 +--- /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__ */ +diff --git a/drivers/mtd/nandx/include/internal/nandx_errno.h b/drivers/mtd/nandx/include/internal/nandx_errno.h +new file mode 100644 +index 0000000000..51fb299c03 +--- /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__ */ +diff --git a/drivers/mtd/nandx/include/internal/nandx_util.h b/drivers/mtd/nandx/include/internal/nandx_util.h +new file mode 100644 +index 0000000000..1990b000ee +--- /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__ */ +diff --git a/drivers/mtd/nandx/include/uboot/nandx_os.h b/drivers/mtd/nandx/include/uboot/nandx_os.h +new file mode 100644 +index 0000000000..8ea53378bf +--- /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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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__ */ +diff --git a/include/configs/mt7622.h b/include/configs/mt7622.h +index dfd506ed24..6d0c956484 100644 +--- a/include/configs/mt7622.h ++++ b/include/configs/mt7622.h +@@ -11,6 +11,31 @@ + + #include + ++/* 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 +-- +2.17.1 + diff --git a/package/boot/uboot-mediatek/patches/003-mt7622-uboot-add-dts-and-config-for-spi-nand.patch b/package/boot/uboot-mediatek/patches/003-mt7622-uboot-add-dts-and-config-for-spi-nand.patch new file mode 100644 index 0000000000..2c021e1c80 --- /dev/null +++ b/package/boot/uboot-mediatek/patches/003-mt7622-uboot-add-dts-and-config-for-spi-nand.patch @@ -0,0 +1,64 @@ +From b1b3c3d2ce62872c8dec4a7d645af6b3c565e094 Mon Sep 17 00:00:00 2001 +From: Sam Shih +Date: Mon, 20 Apr 2020 17:11:32 +0800 +Subject: [PATCH 2/3] mt7622 uboot: add dts and config for spi nand + +This patch add dts and config for mt7622 spi nand + +Signed-off-by: Xiangsheng Hou +--- + arch/arm/dts/mt7622-rfb.dts | 6 ++++++ + arch/arm/dts/mt7622.dtsi | 20 ++++++++++++++++++++ + 2 files changed, 26 insertions(+) + +diff --git a/arch/arm/dts/mt7622-rfb.dts b/arch/arm/dts/mt7622-rfb.dts +index f05c3fe14d..05502bddec 100644 +--- a/arch/arm/dts/mt7622-rfb.dts ++++ b/arch/arm/dts/mt7622-rfb.dts +@@ -143,6 +143,12 @@ + }; + }; + ++&nandc { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&snfi_pins>; ++ status = "okay"; ++}; ++ + &uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins>; +diff --git a/arch/arm/dts/mt7622.dtsi b/arch/arm/dts/mt7622.dtsi +index 1e8ec9b48b..63fdb63d4a 100644 +--- a/arch/arm/dts/mt7622.dtsi ++++ b/arch/arm/dts/mt7622.dtsi +@@ -52,6 +52,26 @@ + #size-cells = <0>; + }; + ++ nandc: nfi@1100d000 { ++ compatible = "mediatek,mt7622-nfc"; ++ reg = <0x1100d000 0x1000>, ++ <0x1100e000 0x1000>; ++ interrupts = , ++ ; ++ clocks = <&pericfg CLK_PERI_NFI_PD>, ++ <&pericfg CLK_PERI_NFIECC_PD>, ++ <&pericfg CLK_PERI_SNFI_PD>, ++ <&topckgen CLK_TOP_NFI_INFRA_SEL>, ++ <&topckgen CLK_TOP_UNIVPLL2_D8>; ++ clock-names = "nfi_clk", ++ "ecc_clk", ++ "snfi_clk", ++ "spinfi_sel", ++ "spinfi_parent_50m"; ++ nand-ecc-mode = "hw"; ++ status = "disabled"; ++ }; ++ + timer { + compatible = "arm,armv8-timer"; + interrupt-parent = <&gic>; +-- +2.17.1 + diff --git a/package/boot/uboot-mediatek/patches/004-configs-enable-mtd-and-mtk_spi_nand-in-defconfig.patch b/package/boot/uboot-mediatek/patches/004-configs-enable-mtd-and-mtk_spi_nand-in-defconfig.patch new file mode 100644 index 0000000000..752d8163f0 --- /dev/null +++ b/package/boot/uboot-mediatek/patches/004-configs-enable-mtd-and-mtk_spi_nand-in-defconfig.patch @@ -0,0 +1,38 @@ +From e5745143a2984cf44fbfc0b3aedb49e57873f109 Mon Sep 17 00:00:00 2001 +From: Sam Shih +Date: Mon, 20 Apr 2020 17:17:04 +0800 +Subject: [PATCH 3/3] configs: enable mtd and mtk_spi_nand in defconfig + +This patch enable mtk and mtk_spi_nand in mt7622_rfb defconfig + +Signed-off-by: Sam Shih +--- + configs/mt7622_rfb_defconfig | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/configs/mt7622_rfb_defconfig b/configs/mt7622_rfb_defconfig +index 1ce6ebdfeb..816126267b 100644 +--- a/configs/mt7622_rfb_defconfig ++++ b/configs/mt7622_rfb_defconfig +@@ -13,6 +13,7 @@ CONFIG_DEFAULT_FDT_FILE="mt7622-rfb" + CONFIG_SYS_PROMPT="MT7622> " + CONFIG_CMD_BOOTMENU=y + CONFIG_CMD_MMC=y ++CONFIG_CMD_NAND=y + CONFIG_CMD_SF_TEST=y + CONFIG_CMD_PING=y + CONFIG_CMD_SMC=y +@@ -25,6 +26,10 @@ CONFIG_CLK=y + CONFIG_DM_MMC=y + CONFIG_MMC_HS200_SUPPORT=y + CONFIG_MMC_MTK=y ++CONFIG_MTD=y ++CONFIG_DM_MTD=y ++CONFIG_MTK_SPI_NAND=y ++CONFIG_MTD_RAW_NAND=y + CONFIG_DM_SPI_FLASH=y + CONFIG_SPI_FLASH_EON=y + CONFIG_SPI_FLASH_GIGADEVICE=y +-- +2.17.1 +