linux/drivers/spi/spi-mtk-snfi.c

// SPDX-License-Identifier: GPL-2.0
//
// Driver for the SPI-NAND mode of Mediatek NAND Flash Interface
//
// Copyright (c) 2022 Chuanhong Guo <[email protected]>
//
// This driver is based on the SPI-NAND mtd driver from Mediatek SDK:
//
// Copyright (C) 2020 MediaTek Inc.
// Author: Weijie Gao <[email protected]>
//
// This controller organize the page data as several interleaved sectors
// like the following: (sizeof(FDM + ECC) = snf->nfi_cfg.spare_size)
// +---------+------+------+---------+------+------+-----+
// | Sector1 | FDM1 | ECC1 | Sector2 | FDM2 | ECC2 | ... |
// +---------+------+------+---------+------+------+-----+
// With auto-format turned on, DMA only returns this part:
// +---------+---------+-----+
// | Sector1 | Sector2 | ... |
// +---------+---------+-----+
// The FDM data will be filled to the registers, and ECC parity data isn't
// accessible.
// With auto-format off, all ((Sector+FDM+ECC)*nsectors) will be read over DMA
// in it's original order shown in the first table. ECC can't be turned on when
// auto-format is off.
//
// However, Linux SPI-NAND driver expects the data returned as:
// +------+-----+
// | Page | OOB |
// +------+-----+
// where the page data is continuously stored instead of interleaved.
// So we assume all instructions matching the page_op template between ECC
// prepare_io_req and finish_io_req are for page cache r/w.
// Here's how this spi-mem driver operates when reading:
//  1. Always set snf->autofmt = true in prepare_io_req (even when ECC is off).
//  2. Perform page ops and let the controller fill the DMA bounce buffer with
//     de-interleaved sector data and set FDM registers.
//  3. Return the data as:
//     +---------+---------+-----+------+------+-----+
//     | Sector1 | Sector2 | ... | FDM1 | FDM2 | ... |
//     +---------+---------+-----+------+------+-----+
//  4. For other matching spi_mem ops outside a prepare/finish_io_req pair,
//     read the data with auto-format off into the bounce buffer and copy
//     needed data to the buffer specified in the request.
//
// Write requests operates in a similar manner.
// As a limitation of this strategy, we won't be able to access any ECC parity
// data at all in Linux.
//
// Here's the bad block mark situation on MTK chips:
// In older chips like mt7622, MTK uses the first FDM byte in the first sector
// as the bad block mark. After de-interleaving, this byte appears at [pagesize]
// in the returned data, which is the BBM position expected by kernel. However,
// the conventional bad block mark is the first byte of the OOB, which is part
// of the last sector data in the interleaved layout. Instead of fixing their
// hardware, MTK decided to address this inconsistency in software. On these
// later chips, the BootROM expects the following:
// 1. The [pagesize] byte on a nand page is used as BBM, which will appear at
//    (page_size - (nsectors - 1) * spare_size) in the DMA buffer.
// 2. The original byte stored at that position in the DMA buffer will be stored
//    as the first byte of the FDM section in the last sector.
// We can't disagree with the BootROM, so after de-interleaving, we need to
// perform the following swaps in read:
// 1. Store the BBM at [page_size - (nsectors - 1) * spare_size] to [page_size],
//    which is the expected BBM position by kernel.
// 2. Store the page data byte at [pagesize + (nsectors-1) * fdm] back to
//    [page_size - (nsectors - 1) * spare_size]
// Similarly, when writing, we need to perform swaps in the other direction.

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/iopoll.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/mtd/nand-ecc-mtk.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
#include <linux/mtd/nand.h>

// NFI registers
#define NFI_CNFG
#define CNFG_OP_MODE_S
#define CNFG_OP_MODE_CUST
#define CNFG_OP_MODE_PROGRAM
#define CNFG_AUTO_FMT_EN
#define CNFG_HW_ECC_EN
#define CNFG_DMA_BURST_EN
#define CNFG_READ_MODE
#define CNFG_DMA_MODE

#define NFI_PAGEFMT
#define NFI_SPARE_SIZE_LS_S
#define NFI_FDM_ECC_NUM_S
#define NFI_FDM_NUM_S
#define NFI_SPARE_SIZE_S
#define NFI_SEC_SEL_512
#define NFI_PAGE_SIZE_S
#define NFI_PAGE_SIZE_512_2K
#define NFI_PAGE_SIZE_2K_4K
#define NFI_PAGE_SIZE_4K_8K
#define NFI_PAGE_SIZE_8K_16K

#define NFI_CON
#define CON_SEC_NUM_S
#define CON_BWR
#define CON_BRD
#define CON_NFI_RST
#define CON_FIFO_FLUSH

#define NFI_INTR_EN
#define NFI_INTR_STA
#define NFI_IRQ_INTR_EN
#define NFI_IRQ_CUS_READ
#define NFI_IRQ_CUS_PG

#define NFI_CMD
#define NFI_CMD_DUMMY_READ
#define NFI_CMD_DUMMY_WRITE

#define NFI_STRDATA
#define STR_DATA

#define NFI_STA
#define NFI_NAND_FSM_7622
#define NFI_NAND_FSM_7986
#define NFI_FSM
#define READ_EMPTY

#define NFI_FIFOSTA
#define FIFO_WR_REMAIN_S
#define FIFO_RD_REMAIN_S

#define NFI_ADDRCNTR
#define SEC_CNTR
#define SEC_CNTR_S
#define NFI_SEC_CNTR(val)

#define NFI_STRADDR

#define NFI_BYTELEN
#define BUS_SEC_CNTR(val)

#define NFI_FDM0L
#define NFI_FDM0M
#define NFI_FDML(n)
#define NFI_FDMM(n)

#define NFI_DEBUG_CON1
#define WBUF_EN

#define NFI_MASTERSTA
#define MAS_ADDR
#define MAS_RD
#define MAS_WR
#define MAS_RDDLY
#define NFI_MASTERSTA_MASK_7622
#define NFI_MASTERSTA_MASK_7986

// SNFI registers
#define SNF_MAC_CTL
#define MAC_XIO_SEL
#define SF_MAC_EN
#define SF_TRIG
#define WIP_READY
#define WIP

#define SNF_MAC_OUTL
#define SNF_MAC_INL

#define SNF_RD_CTL2
#define DATA_READ_DUMMY_S
#define DATA_READ_MAX_DUMMY
#define DATA_READ_CMD_S

#define SNF_RD_CTL3

#define SNF_PG_CTL1
#define PG_LOAD_CMD_S

#define SNF_PG_CTL2

#define SNF_MISC_CTL
#define SW_RST
#define FIFO_RD_LTC_S
#define PG_LOAD_X4_EN
#define DATA_READ_MODE_S
#define DATA_READ_MODE
#define DATA_READ_MODE_X1
#define DATA_READ_MODE_X2
#define DATA_READ_MODE_X4
#define DATA_READ_MODE_DUAL
#define DATA_READ_MODE_QUAD
#define DATA_READ_LATCH_LAT
#define DATA_READ_LATCH_LAT_S
#define PG_LOAD_CUSTOM_EN
#define DATARD_CUSTOM_EN
#define CS_DESELECT_CYC_S

#define SNF_MISC_CTL2
#define PROGRAM_LOAD_BYTE_NUM_S
#define READ_DATA_BYTE_NUM_S

#define SNF_DLY_CTL3
#define SFCK_SAM_DLY_S
#define SFCK_SAM_DLY
#define SFCK_SAM_DLY_TOTAL
#define SFCK_SAM_DLY_RANGE

#define SNF_STA_CTL1
#define CUS_PG_DONE
#define CUS_READ_DONE
#define SPI_STATE_S
#define SPI_STATE

#define SNF_CFG
#define SPI_MODE

#define SNF_GPRAM
#define SNF_GPRAM_SIZE

#define SNFI_POLL_INTERVAL

static const u8 mt7622_spare_sizes[] =;

static const u8 mt7986_spare_sizes[] =;

struct mtk_snand_caps {};

static const struct mtk_snand_caps mt7622_snand_caps =;

static const struct mtk_snand_caps mt7629_snand_caps =;

static const struct mtk_snand_caps mt7986_snand_caps =;

struct mtk_snand_conf {};

struct mtk_snand {};

static struct mtk_snand *nand_to_mtk_snand(struct nand_device *nand)
{}

static inline int snand_prepare_bouncebuf(struct mtk_snand *snf, size_t size)
{}

static inline u32 nfi_read32(struct mtk_snand *snf, u32 reg)
{}

static inline void nfi_write32(struct mtk_snand *snf, u32 reg, u32 val)
{}

static inline void nfi_write16(struct mtk_snand *snf, u32 reg, u16 val)
{}

static inline void nfi_rmw32(struct mtk_snand *snf, u32 reg, u32 clr, u32 set)
{}

static void nfi_read_data(struct mtk_snand *snf, u32 reg, u8 *data, u32 len)
{}

static int mtk_nfi_reset(struct mtk_snand *snf)
{}

static int mtk_snand_mac_reset(struct mtk_snand *snf)
{}

static int mtk_snand_mac_trigger(struct mtk_snand *snf, u32 outlen, u32 inlen)
{}

static int mtk_snand_mac_io(struct mtk_snand *snf, const struct spi_mem_op *op)
{}

static int mtk_snand_setup_pagefmt(struct mtk_snand *snf, u32 page_size,
				   u32 oob_size)
{}

static int mtk_snand_ooblayout_ecc(struct mtd_info *mtd, int section,
				   struct mtd_oob_region *oobecc)
{}

static int mtk_snand_ooblayout_free(struct mtd_info *mtd, int section,
				    struct mtd_oob_region *oobfree)
{}

static const struct mtd_ooblayout_ops mtk_snand_ooblayout =;

static int mtk_snand_ecc_init_ctx(struct nand_device *nand)
{}

static void mtk_snand_ecc_cleanup_ctx(struct nand_device *nand)
{}

static int mtk_snand_ecc_prepare_io_req(struct nand_device *nand,
					struct nand_page_io_req *req)
{}

static int mtk_snand_ecc_finish_io_req(struct nand_device *nand,
				       struct nand_page_io_req *req)
{}

static struct nand_ecc_engine_ops mtk_snfi_ecc_engine_ops =;

static void mtk_snand_read_fdm(struct mtk_snand *snf, u8 *buf)
{}

static void mtk_snand_write_fdm(struct mtk_snand *snf, const u8 *buf)
{}

static void mtk_snand_bm_swap(struct mtk_snand *snf, u8 *buf)
{}

static void mtk_snand_fdm_bm_swap(struct mtk_snand *snf)
{}

static int mtk_snand_read_page_cache(struct mtk_snand *snf,
				     const struct spi_mem_op *op)
{}

static int mtk_snand_write_page_cache(struct mtk_snand *snf,
				      const struct spi_mem_op *op)
{}

/**
 * mtk_snand_is_page_ops() - check if the op is a controller supported page op.
 * @op: spi-mem op to check
 *
 * Check whether op can be executed with read_from_cache or program_load
 * mode in the controller.
 * This controller can execute typical Read From Cache and Program Load
 * instructions found on SPI-NAND with 2-byte address.
 * DTR and cmd buswidth & nbytes should be checked before calling this.
 *
 * Return: true if the op matches the instruction template
 */
static bool mtk_snand_is_page_ops(const struct spi_mem_op *op)
{}

static bool mtk_snand_supports_op(struct spi_mem *mem,
				  const struct spi_mem_op *op)
{}

static int mtk_snand_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
{}

static int mtk_snand_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
{}

static const struct spi_controller_mem_ops mtk_snand_mem_ops =;

static const struct spi_controller_mem_caps mtk_snand_mem_caps =;

static irqreturn_t mtk_snand_irq(int irq, void *id)
{}

static const struct of_device_id mtk_snand_ids[] =;

MODULE_DEVICE_TABLE(of, mtk_snand_ids);

static int mtk_snand_probe(struct platform_device *pdev)
{}

static void mtk_snand_remove(struct platform_device *pdev)
{}

static struct platform_driver mtk_snand_driver =;

module_platform_driver();

MODULE_LICENSE();
MODULE_AUTHOR();
MODULE_DESCRIPTION();