nand: raw: add support for MediaTek MT7621 SoC

This patch adds NAND flash controller driver for MediaTek MT7621 SoC.
The NAND flash controller of MT7621 supports only SLC NAND flashes.
It supports 4~12 bits correction with maximum 4KB page size.

Signed-off-by: Weijie Gao <weijie.gao@mediatek.com>
This commit is contained in:
Weijie Gao
2022-05-20 11:23:47 +08:00
committed by Daniel Schwierzeck
parent ad80d48979
commit 3ab8beaadc
5 changed files with 1488 additions and 2 deletions

View File

@@ -526,12 +526,25 @@ config TEGRA_NAND
help
Enables support for NAND Flash chips on Tegra SoCs platforms.
config NAND_MT7621
bool "Support for MediaTek MT7621 NAND flash controller"
depends on SOC_MT7621
select SYS_NAND_SELF_INIT
select SPL_SYS_NAND_SELF_INIT
imply CMD_NAND
help
This enables NAND driver for the NAND flash controller on MediaTek
MT7621 platform.
The controller supports 4~12 bits correction per 512 bytes with a
maximum 4KB page size.
comment "Generic NAND options"
config SYS_NAND_BLOCK_SIZE
hex "NAND chip eraseblock size"
depends on ARCH_SUNXI || SPL_NAND_SUPPORT || TPL_NAND_SUPPORT
depends on !NAND_MXS && !NAND_DENALI_DT && !NAND_LPC32XX_MLC && !NAND_FSL_IFC
depends on !NAND_MXS && !NAND_DENALI_DT && !NAND_LPC32XX_MLC && \
!NAND_FSL_IFC && !NAND_MT7621
help
Number of data bytes in one eraseblock for the NAND chip on the
board. This is the multiple of NAND_PAGE_SIZE and the number of
@@ -556,7 +569,7 @@ config SYS_NAND_PAGE_SIZE
depends on ARCH_SUNXI || NAND_OMAP_GPMC || NAND_LPC32XX_SLC || \
SPL_NAND_SIMPLE || (NAND_MXC && SPL_NAND_SUPPORT) || \
(NAND_ATMEL && SPL_NAND_SUPPORT) || SPL_GENERATE_ATMEL_PMECC_HEADER
depends on !NAND_MXS && !NAND_DENALI_DT && !NAND_LPC32XX_MLC
depends on !NAND_MXS && !NAND_DENALI_DT && !NAND_LPC32XX_MLC && !NAND_MT7621
help
Number of data bytes in one page for the NAND chip on the
board, not including the OOB area.

View File

@@ -72,6 +72,7 @@ obj-$(CONFIG_NAND_ZYNQ) += zynq_nand.o
obj-$(CONFIG_NAND_STM32_FMC2) += stm32_fmc2_nand.o
obj-$(CONFIG_CORTINA_NAND) += cortina_nand.o
obj-$(CONFIG_ROCKCHIP_NAND) += rockchip_nfc.o
obj-$(CONFIG_NAND_MT7621) += mt7621_nand.o
else # minimal SPL drivers
@@ -80,5 +81,6 @@ obj-$(CONFIG_NAND_FSL_IFC) += fsl_ifc_spl.o
obj-$(CONFIG_NAND_MXC) += mxc_nand_spl.o
obj-$(CONFIG_NAND_MXS) += mxs_nand_spl.o mxs_nand.o
obj-$(CONFIG_NAND_SUNXI) += sunxi_nand_spl.o
obj-$(CONFIG_NAND_MT7621) += mt7621_nand_spl.o mt7621_nand.o
endif # drivers

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2022 MediaTek Inc. All rights reserved.
*
* Author: Weijie Gao <weijie.gao@mediatek.com>
*/
#ifndef _MT7621_NAND_H_
#define _MT7621_NAND_H_
#include <linux/types.h>
#include <linux/mtd/mtd.h>
#include <linux/compiler.h>
#include <linux/mtd/rawnand.h>
struct mt7621_nfc {
struct nand_chip nand;
void __iomem *nfi_regs;
void __iomem *ecc_regs;
u32 spare_per_sector;
};
/* for SPL */
void mt7621_nfc_spl_init(struct mt7621_nfc *nfc);
int mt7621_nfc_spl_post_init(struct mt7621_nfc *nfc);
#endif /* _MT7621_NAND_H_ */

View File

@@ -0,0 +1,237 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 MediaTek Inc. All rights reserved.
*
* Author: Weijie Gao <weijie.gao@mediatek.com>
*/
#include <image.h>
#include <malloc.h>
#include <linux/sizes.h>
#include <linux/delay.h>
#include <linux/mtd/rawnand.h>
#include "mt7621_nand.h"
static struct mt7621_nfc nfc_dev;
static u8 *buffer;
static int nand_valid;
static void nand_command_lp(struct mtd_info *mtd, unsigned int command,
int column, int page_addr)
{
register struct nand_chip *chip = mtd_to_nand(mtd);
/* Command latch cycle */
chip->cmd_ctrl(mtd, command, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
if (column != -1 || page_addr != -1) {
int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE;
/* Serially input address */
if (column != -1) {
chip->cmd_ctrl(mtd, column, ctrl);
ctrl &= ~NAND_CTRL_CHANGE;
if (command != NAND_CMD_READID)
chip->cmd_ctrl(mtd, column >> 8, ctrl);
}
if (page_addr != -1) {
chip->cmd_ctrl(mtd, page_addr, ctrl);
chip->cmd_ctrl(mtd, page_addr >> 8,
NAND_NCE | NAND_ALE);
if (chip->options & NAND_ROW_ADDR_3)
chip->cmd_ctrl(mtd, page_addr >> 16,
NAND_NCE | NAND_ALE);
}
}
chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
/*
* Program and erase have their own busy handlers status, sequential
* in and status need no delay.
*/
switch (command) {
case NAND_CMD_STATUS:
case NAND_CMD_READID:
case NAND_CMD_SET_FEATURES:
return;
case NAND_CMD_READ0:
chip->cmd_ctrl(mtd, NAND_CMD_READSTART,
NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
chip->cmd_ctrl(mtd, NAND_CMD_NONE,
NAND_NCE | NAND_CTRL_CHANGE);
}
/*
* Apply this short delay always to ensure that we do wait tWB in
* any case on any machine.
*/
ndelay(100);
nand_wait_ready(mtd);
}
static int nfc_read_page_hwecc(struct mtd_info *mtd, void *buf,
unsigned int page)
{
struct nand_chip *chip = mtd_to_nand(mtd);
int ret;
chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, page);
ret = chip->ecc.read_page(mtd, chip, buf, 1, page);
if (ret < 0 || ret > chip->ecc.strength)
return -1;
return 0;
}
static int nfc_read_oob_hwecc(struct mtd_info *mtd, void *buf, u32 len,
unsigned int page)
{
struct nand_chip *chip = mtd_to_nand(mtd);
int ret;
chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, page);
ret = chip->ecc.read_page(mtd, chip, NULL, 1, page);
if (ret < 0)
return -1;
if (len > mtd->oobsize)
len = mtd->oobsize;
memcpy(buf, chip->oob_poi, len);
return 0;
}
static int nfc_check_bad_block(struct mtd_info *mtd, unsigned int page)
{
struct nand_chip *chip = mtd_to_nand(mtd);
u32 pages_per_block, i = 0;
int ret;
u8 bad;
pages_per_block = 1 << (mtd->erasesize_shift - mtd->writesize_shift);
/* Read from first/last page(s) if necessary */
if (chip->bbt_options & NAND_BBT_SCANLASTPAGE) {
page += pages_per_block - 1;
if (chip->bbt_options & NAND_BBT_SCAN2NDPAGE)
page--;
}
do {
ret = nfc_read_oob_hwecc(mtd, &bad, 1, page);
if (ret)
return ret;
ret = bad != 0xFF;
i++;
page++;
} while (!ret && (chip->bbt_options & NAND_BBT_SCAN2NDPAGE) && i < 2);
return ret;
}
int nand_spl_load_image(uint32_t offs, unsigned int size, void *dest)
{
struct mt7621_nfc *nfc = &nfc_dev;
struct nand_chip *chip = &nfc->nand;
struct mtd_info *mtd = &chip->mtd;
u32 addr, col, page, chksz;
bool check_bad = true;
if (!nand_valid)
return -ENODEV;
while (size) {
if (check_bad || !(offs & mtd->erasesize_mask)) {
addr = offs & (~mtd->erasesize_mask);
page = addr >> mtd->writesize_shift;
if (nfc_check_bad_block(mtd, page)) {
/* Skip bad block */
if (addr >= mtd->size - mtd->erasesize)
return -1;
offs += mtd->erasesize;
continue;
}
check_bad = false;
}
col = offs & mtd->writesize_mask;
page = offs >> mtd->writesize_shift;
chksz = min(mtd->writesize - col, (uint32_t)size);
if (unlikely(chksz < mtd->writesize)) {
/* Not reading a full page */
if (nfc_read_page_hwecc(mtd, buffer, page))
return -1;
memcpy(dest, buffer + col, chksz);
} else {
if (nfc_read_page_hwecc(mtd, dest, page))
return -1;
}
dest += chksz;
offs += chksz;
size -= chksz;
}
return 0;
}
int nand_default_bbt(struct mtd_info *mtd)
{
return 0;
}
unsigned long nand_size(void)
{
if (!nand_valid)
return 0;
/* Unlikely that NAND size > 2GBytes */
if (nfc_dev.nand.chipsize <= SZ_2G)
return nfc_dev.nand.chipsize;
return SZ_2G;
}
void nand_deselect(void)
{
}
void nand_init(void)
{
struct mtd_info *mtd;
struct nand_chip *chip;
if (nand_valid)
return;
mt7621_nfc_spl_init(&nfc_dev);
chip = &nfc_dev.nand;
mtd = &chip->mtd;
chip->cmdfunc = nand_command_lp;
if (mt7621_nfc_spl_post_init(&nfc_dev))
return;
mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
mtd->writesize_shift = ffs(mtd->writesize) - 1;
mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
buffer = malloc(mtd->writesize);
if (!buffer)
return;
nand_valid = 1;
}