162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci//
362306a36Sopenharmony_ci// Driver for the SPI-NAND mode of Mediatek NAND Flash Interface
462306a36Sopenharmony_ci//
562306a36Sopenharmony_ci// Copyright (c) 2022 Chuanhong Guo <gch981213@gmail.com>
662306a36Sopenharmony_ci//
762306a36Sopenharmony_ci// This driver is based on the SPI-NAND mtd driver from Mediatek SDK:
862306a36Sopenharmony_ci//
962306a36Sopenharmony_ci// Copyright (C) 2020 MediaTek Inc.
1062306a36Sopenharmony_ci// Author: Weijie Gao <weijie.gao@mediatek.com>
1162306a36Sopenharmony_ci//
1262306a36Sopenharmony_ci// This controller organize the page data as several interleaved sectors
1362306a36Sopenharmony_ci// like the following: (sizeof(FDM + ECC) = snf->nfi_cfg.spare_size)
1462306a36Sopenharmony_ci// +---------+------+------+---------+------+------+-----+
1562306a36Sopenharmony_ci// | Sector1 | FDM1 | ECC1 | Sector2 | FDM2 | ECC2 | ... |
1662306a36Sopenharmony_ci// +---------+------+------+---------+------+------+-----+
1762306a36Sopenharmony_ci// With auto-format turned on, DMA only returns this part:
1862306a36Sopenharmony_ci// +---------+---------+-----+
1962306a36Sopenharmony_ci// | Sector1 | Sector2 | ... |
2062306a36Sopenharmony_ci// +---------+---------+-----+
2162306a36Sopenharmony_ci// The FDM data will be filled to the registers, and ECC parity data isn't
2262306a36Sopenharmony_ci// accessible.
2362306a36Sopenharmony_ci// With auto-format off, all ((Sector+FDM+ECC)*nsectors) will be read over DMA
2462306a36Sopenharmony_ci// in it's original order shown in the first table. ECC can't be turned on when
2562306a36Sopenharmony_ci// auto-format is off.
2662306a36Sopenharmony_ci//
2762306a36Sopenharmony_ci// However, Linux SPI-NAND driver expects the data returned as:
2862306a36Sopenharmony_ci// +------+-----+
2962306a36Sopenharmony_ci// | Page | OOB |
3062306a36Sopenharmony_ci// +------+-----+
3162306a36Sopenharmony_ci// where the page data is continuously stored instead of interleaved.
3262306a36Sopenharmony_ci// So we assume all instructions matching the page_op template between ECC
3362306a36Sopenharmony_ci// prepare_io_req and finish_io_req are for page cache r/w.
3462306a36Sopenharmony_ci// Here's how this spi-mem driver operates when reading:
3562306a36Sopenharmony_ci//  1. Always set snf->autofmt = true in prepare_io_req (even when ECC is off).
3662306a36Sopenharmony_ci//  2. Perform page ops and let the controller fill the DMA bounce buffer with
3762306a36Sopenharmony_ci//     de-interleaved sector data and set FDM registers.
3862306a36Sopenharmony_ci//  3. Return the data as:
3962306a36Sopenharmony_ci//     +---------+---------+-----+------+------+-----+
4062306a36Sopenharmony_ci//     | Sector1 | Sector2 | ... | FDM1 | FDM2 | ... |
4162306a36Sopenharmony_ci//     +---------+---------+-----+------+------+-----+
4262306a36Sopenharmony_ci//  4. For other matching spi_mem ops outside a prepare/finish_io_req pair,
4362306a36Sopenharmony_ci//     read the data with auto-format off into the bounce buffer and copy
4462306a36Sopenharmony_ci//     needed data to the buffer specified in the request.
4562306a36Sopenharmony_ci//
4662306a36Sopenharmony_ci// Write requests operates in a similar manner.
4762306a36Sopenharmony_ci// As a limitation of this strategy, we won't be able to access any ECC parity
4862306a36Sopenharmony_ci// data at all in Linux.
4962306a36Sopenharmony_ci//
5062306a36Sopenharmony_ci// Here's the bad block mark situation on MTK chips:
5162306a36Sopenharmony_ci// In older chips like mt7622, MTK uses the first FDM byte in the first sector
5262306a36Sopenharmony_ci// as the bad block mark. After de-interleaving, this byte appears at [pagesize]
5362306a36Sopenharmony_ci// in the returned data, which is the BBM position expected by kernel. However,
5462306a36Sopenharmony_ci// the conventional bad block mark is the first byte of the OOB, which is part
5562306a36Sopenharmony_ci// of the last sector data in the interleaved layout. Instead of fixing their
5662306a36Sopenharmony_ci// hardware, MTK decided to address this inconsistency in software. On these
5762306a36Sopenharmony_ci// later chips, the BootROM expects the following:
5862306a36Sopenharmony_ci// 1. The [pagesize] byte on a nand page is used as BBM, which will appear at
5962306a36Sopenharmony_ci//    (page_size - (nsectors - 1) * spare_size) in the DMA buffer.
6062306a36Sopenharmony_ci// 2. The original byte stored at that position in the DMA buffer will be stored
6162306a36Sopenharmony_ci//    as the first byte of the FDM section in the last sector.
6262306a36Sopenharmony_ci// We can't disagree with the BootROM, so after de-interleaving, we need to
6362306a36Sopenharmony_ci// perform the following swaps in read:
6462306a36Sopenharmony_ci// 1. Store the BBM at [page_size - (nsectors - 1) * spare_size] to [page_size],
6562306a36Sopenharmony_ci//    which is the expected BBM position by kernel.
6662306a36Sopenharmony_ci// 2. Store the page data byte at [pagesize + (nsectors-1) * fdm] back to
6762306a36Sopenharmony_ci//    [page_size - (nsectors - 1) * spare_size]
6862306a36Sopenharmony_ci// Similarly, when writing, we need to perform swaps in the other direction.
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci#include <linux/kernel.h>
7162306a36Sopenharmony_ci#include <linux/module.h>
7262306a36Sopenharmony_ci#include <linux/init.h>
7362306a36Sopenharmony_ci#include <linux/device.h>
7462306a36Sopenharmony_ci#include <linux/mutex.h>
7562306a36Sopenharmony_ci#include <linux/clk.h>
7662306a36Sopenharmony_ci#include <linux/interrupt.h>
7762306a36Sopenharmony_ci#include <linux/dma-mapping.h>
7862306a36Sopenharmony_ci#include <linux/iopoll.h>
7962306a36Sopenharmony_ci#include <linux/of.h>
8062306a36Sopenharmony_ci#include <linux/platform_device.h>
8162306a36Sopenharmony_ci#include <linux/mtd/nand-ecc-mtk.h>
8262306a36Sopenharmony_ci#include <linux/spi/spi.h>
8362306a36Sopenharmony_ci#include <linux/spi/spi-mem.h>
8462306a36Sopenharmony_ci#include <linux/mtd/nand.h>
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci// NFI registers
8762306a36Sopenharmony_ci#define NFI_CNFG 0x000
8862306a36Sopenharmony_ci#define CNFG_OP_MODE_S 12
8962306a36Sopenharmony_ci#define CNFG_OP_MODE_CUST 6
9062306a36Sopenharmony_ci#define CNFG_OP_MODE_PROGRAM 3
9162306a36Sopenharmony_ci#define CNFG_AUTO_FMT_EN BIT(9)
9262306a36Sopenharmony_ci#define CNFG_HW_ECC_EN BIT(8)
9362306a36Sopenharmony_ci#define CNFG_DMA_BURST_EN BIT(2)
9462306a36Sopenharmony_ci#define CNFG_READ_MODE BIT(1)
9562306a36Sopenharmony_ci#define CNFG_DMA_MODE BIT(0)
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci#define NFI_PAGEFMT 0x0004
9862306a36Sopenharmony_ci#define NFI_SPARE_SIZE_LS_S 16
9962306a36Sopenharmony_ci#define NFI_FDM_ECC_NUM_S 12
10062306a36Sopenharmony_ci#define NFI_FDM_NUM_S 8
10162306a36Sopenharmony_ci#define NFI_SPARE_SIZE_S 4
10262306a36Sopenharmony_ci#define NFI_SEC_SEL_512 BIT(2)
10362306a36Sopenharmony_ci#define NFI_PAGE_SIZE_S 0
10462306a36Sopenharmony_ci#define NFI_PAGE_SIZE_512_2K 0
10562306a36Sopenharmony_ci#define NFI_PAGE_SIZE_2K_4K 1
10662306a36Sopenharmony_ci#define NFI_PAGE_SIZE_4K_8K 2
10762306a36Sopenharmony_ci#define NFI_PAGE_SIZE_8K_16K 3
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci#define NFI_CON 0x008
11062306a36Sopenharmony_ci#define CON_SEC_NUM_S 12
11162306a36Sopenharmony_ci#define CON_BWR BIT(9)
11262306a36Sopenharmony_ci#define CON_BRD BIT(8)
11362306a36Sopenharmony_ci#define CON_NFI_RST BIT(1)
11462306a36Sopenharmony_ci#define CON_FIFO_FLUSH BIT(0)
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci#define NFI_INTR_EN 0x010
11762306a36Sopenharmony_ci#define NFI_INTR_STA 0x014
11862306a36Sopenharmony_ci#define NFI_IRQ_INTR_EN BIT(31)
11962306a36Sopenharmony_ci#define NFI_IRQ_CUS_READ BIT(8)
12062306a36Sopenharmony_ci#define NFI_IRQ_CUS_PG BIT(7)
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci#define NFI_CMD 0x020
12362306a36Sopenharmony_ci#define NFI_CMD_DUMMY_READ 0x00
12462306a36Sopenharmony_ci#define NFI_CMD_DUMMY_WRITE 0x80
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci#define NFI_STRDATA 0x040
12762306a36Sopenharmony_ci#define STR_DATA BIT(0)
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci#define NFI_STA 0x060
13062306a36Sopenharmony_ci#define NFI_NAND_FSM_7622 GENMASK(28, 24)
13162306a36Sopenharmony_ci#define NFI_NAND_FSM_7986 GENMASK(29, 23)
13262306a36Sopenharmony_ci#define NFI_FSM GENMASK(19, 16)
13362306a36Sopenharmony_ci#define READ_EMPTY BIT(12)
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci#define NFI_FIFOSTA 0x064
13662306a36Sopenharmony_ci#define FIFO_WR_REMAIN_S 8
13762306a36Sopenharmony_ci#define FIFO_RD_REMAIN_S 0
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci#define NFI_ADDRCNTR 0x070
14062306a36Sopenharmony_ci#define SEC_CNTR GENMASK(16, 12)
14162306a36Sopenharmony_ci#define SEC_CNTR_S 12
14262306a36Sopenharmony_ci#define NFI_SEC_CNTR(val) (((val)&SEC_CNTR) >> SEC_CNTR_S)
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci#define NFI_STRADDR 0x080
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci#define NFI_BYTELEN 0x084
14762306a36Sopenharmony_ci#define BUS_SEC_CNTR(val) (((val)&SEC_CNTR) >> SEC_CNTR_S)
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci#define NFI_FDM0L 0x0a0
15062306a36Sopenharmony_ci#define NFI_FDM0M 0x0a4
15162306a36Sopenharmony_ci#define NFI_FDML(n) (NFI_FDM0L + (n)*8)
15262306a36Sopenharmony_ci#define NFI_FDMM(n) (NFI_FDM0M + (n)*8)
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci#define NFI_DEBUG_CON1 0x220
15562306a36Sopenharmony_ci#define WBUF_EN BIT(2)
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci#define NFI_MASTERSTA 0x224
15862306a36Sopenharmony_ci#define MAS_ADDR GENMASK(11, 9)
15962306a36Sopenharmony_ci#define MAS_RD GENMASK(8, 6)
16062306a36Sopenharmony_ci#define MAS_WR GENMASK(5, 3)
16162306a36Sopenharmony_ci#define MAS_RDDLY GENMASK(2, 0)
16262306a36Sopenharmony_ci#define NFI_MASTERSTA_MASK_7622 (MAS_ADDR | MAS_RD | MAS_WR | MAS_RDDLY)
16362306a36Sopenharmony_ci#define NFI_MASTERSTA_MASK_7986 3
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci// SNFI registers
16662306a36Sopenharmony_ci#define SNF_MAC_CTL 0x500
16762306a36Sopenharmony_ci#define MAC_XIO_SEL BIT(4)
16862306a36Sopenharmony_ci#define SF_MAC_EN BIT(3)
16962306a36Sopenharmony_ci#define SF_TRIG BIT(2)
17062306a36Sopenharmony_ci#define WIP_READY BIT(1)
17162306a36Sopenharmony_ci#define WIP BIT(0)
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci#define SNF_MAC_OUTL 0x504
17462306a36Sopenharmony_ci#define SNF_MAC_INL 0x508
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci#define SNF_RD_CTL2 0x510
17762306a36Sopenharmony_ci#define DATA_READ_DUMMY_S 8
17862306a36Sopenharmony_ci#define DATA_READ_MAX_DUMMY 0xf
17962306a36Sopenharmony_ci#define DATA_READ_CMD_S 0
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci#define SNF_RD_CTL3 0x514
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci#define SNF_PG_CTL1 0x524
18462306a36Sopenharmony_ci#define PG_LOAD_CMD_S 8
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci#define SNF_PG_CTL2 0x528
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci#define SNF_MISC_CTL 0x538
18962306a36Sopenharmony_ci#define SW_RST BIT(28)
19062306a36Sopenharmony_ci#define FIFO_RD_LTC_S 25
19162306a36Sopenharmony_ci#define PG_LOAD_X4_EN BIT(20)
19262306a36Sopenharmony_ci#define DATA_READ_MODE_S 16
19362306a36Sopenharmony_ci#define DATA_READ_MODE GENMASK(18, 16)
19462306a36Sopenharmony_ci#define DATA_READ_MODE_X1 0
19562306a36Sopenharmony_ci#define DATA_READ_MODE_X2 1
19662306a36Sopenharmony_ci#define DATA_READ_MODE_X4 2
19762306a36Sopenharmony_ci#define DATA_READ_MODE_DUAL 5
19862306a36Sopenharmony_ci#define DATA_READ_MODE_QUAD 6
19962306a36Sopenharmony_ci#define DATA_READ_LATCH_LAT GENMASK(9, 8)
20062306a36Sopenharmony_ci#define DATA_READ_LATCH_LAT_S 8
20162306a36Sopenharmony_ci#define PG_LOAD_CUSTOM_EN BIT(7)
20262306a36Sopenharmony_ci#define DATARD_CUSTOM_EN BIT(6)
20362306a36Sopenharmony_ci#define CS_DESELECT_CYC_S 0
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci#define SNF_MISC_CTL2 0x53c
20662306a36Sopenharmony_ci#define PROGRAM_LOAD_BYTE_NUM_S 16
20762306a36Sopenharmony_ci#define READ_DATA_BYTE_NUM_S 11
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci#define SNF_DLY_CTL3 0x548
21062306a36Sopenharmony_ci#define SFCK_SAM_DLY_S 0
21162306a36Sopenharmony_ci#define SFCK_SAM_DLY GENMASK(5, 0)
21262306a36Sopenharmony_ci#define SFCK_SAM_DLY_TOTAL 9
21362306a36Sopenharmony_ci#define SFCK_SAM_DLY_RANGE 47
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci#define SNF_STA_CTL1 0x550
21662306a36Sopenharmony_ci#define CUS_PG_DONE BIT(28)
21762306a36Sopenharmony_ci#define CUS_READ_DONE BIT(27)
21862306a36Sopenharmony_ci#define SPI_STATE_S 0
21962306a36Sopenharmony_ci#define SPI_STATE GENMASK(3, 0)
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci#define SNF_CFG 0x55c
22262306a36Sopenharmony_ci#define SPI_MODE BIT(0)
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci#define SNF_GPRAM 0x800
22562306a36Sopenharmony_ci#define SNF_GPRAM_SIZE 0xa0
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci#define SNFI_POLL_INTERVAL 1000000
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_cistatic const u8 mt7622_spare_sizes[] = { 16, 26, 27, 28 };
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic const u8 mt7986_spare_sizes[] = {
23262306a36Sopenharmony_ci	16, 26, 27, 28, 32, 36, 40, 44, 48, 49, 50, 51, 52, 62, 61, 63, 64, 67,
23362306a36Sopenharmony_ci	74
23462306a36Sopenharmony_ci};
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistruct mtk_snand_caps {
23762306a36Sopenharmony_ci	u16 sector_size;
23862306a36Sopenharmony_ci	u16 max_sectors;
23962306a36Sopenharmony_ci	u16 fdm_size;
24062306a36Sopenharmony_ci	u16 fdm_ecc_size;
24162306a36Sopenharmony_ci	u16 fifo_size;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	bool bbm_swap;
24462306a36Sopenharmony_ci	bool empty_page_check;
24562306a36Sopenharmony_ci	u32 mastersta_mask;
24662306a36Sopenharmony_ci	u32 nandfsm_mask;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	const u8 *spare_sizes;
24962306a36Sopenharmony_ci	u32 num_spare_size;
25062306a36Sopenharmony_ci};
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_cistatic const struct mtk_snand_caps mt7622_snand_caps = {
25362306a36Sopenharmony_ci	.sector_size = 512,
25462306a36Sopenharmony_ci	.max_sectors = 8,
25562306a36Sopenharmony_ci	.fdm_size = 8,
25662306a36Sopenharmony_ci	.fdm_ecc_size = 1,
25762306a36Sopenharmony_ci	.fifo_size = 32,
25862306a36Sopenharmony_ci	.bbm_swap = false,
25962306a36Sopenharmony_ci	.empty_page_check = false,
26062306a36Sopenharmony_ci	.mastersta_mask = NFI_MASTERSTA_MASK_7622,
26162306a36Sopenharmony_ci	.nandfsm_mask = NFI_NAND_FSM_7622,
26262306a36Sopenharmony_ci	.spare_sizes = mt7622_spare_sizes,
26362306a36Sopenharmony_ci	.num_spare_size = ARRAY_SIZE(mt7622_spare_sizes)
26462306a36Sopenharmony_ci};
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_cistatic const struct mtk_snand_caps mt7629_snand_caps = {
26762306a36Sopenharmony_ci	.sector_size = 512,
26862306a36Sopenharmony_ci	.max_sectors = 8,
26962306a36Sopenharmony_ci	.fdm_size = 8,
27062306a36Sopenharmony_ci	.fdm_ecc_size = 1,
27162306a36Sopenharmony_ci	.fifo_size = 32,
27262306a36Sopenharmony_ci	.bbm_swap = true,
27362306a36Sopenharmony_ci	.empty_page_check = false,
27462306a36Sopenharmony_ci	.mastersta_mask = NFI_MASTERSTA_MASK_7622,
27562306a36Sopenharmony_ci	.nandfsm_mask = NFI_NAND_FSM_7622,
27662306a36Sopenharmony_ci	.spare_sizes = mt7622_spare_sizes,
27762306a36Sopenharmony_ci	.num_spare_size = ARRAY_SIZE(mt7622_spare_sizes)
27862306a36Sopenharmony_ci};
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic const struct mtk_snand_caps mt7986_snand_caps = {
28162306a36Sopenharmony_ci	.sector_size = 1024,
28262306a36Sopenharmony_ci	.max_sectors = 8,
28362306a36Sopenharmony_ci	.fdm_size = 8,
28462306a36Sopenharmony_ci	.fdm_ecc_size = 1,
28562306a36Sopenharmony_ci	.fifo_size = 64,
28662306a36Sopenharmony_ci	.bbm_swap = true,
28762306a36Sopenharmony_ci	.empty_page_check = true,
28862306a36Sopenharmony_ci	.mastersta_mask = NFI_MASTERSTA_MASK_7986,
28962306a36Sopenharmony_ci	.nandfsm_mask = NFI_NAND_FSM_7986,
29062306a36Sopenharmony_ci	.spare_sizes = mt7986_spare_sizes,
29162306a36Sopenharmony_ci	.num_spare_size = ARRAY_SIZE(mt7986_spare_sizes)
29262306a36Sopenharmony_ci};
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_cistruct mtk_snand_conf {
29562306a36Sopenharmony_ci	size_t page_size;
29662306a36Sopenharmony_ci	size_t oob_size;
29762306a36Sopenharmony_ci	u8 nsectors;
29862306a36Sopenharmony_ci	u8 spare_size;
29962306a36Sopenharmony_ci};
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_cistruct mtk_snand {
30262306a36Sopenharmony_ci	struct spi_controller *ctlr;
30362306a36Sopenharmony_ci	struct device *dev;
30462306a36Sopenharmony_ci	struct clk *nfi_clk;
30562306a36Sopenharmony_ci	struct clk *pad_clk;
30662306a36Sopenharmony_ci	struct clk *nfi_hclk;
30762306a36Sopenharmony_ci	void __iomem *nfi_base;
30862306a36Sopenharmony_ci	int irq;
30962306a36Sopenharmony_ci	struct completion op_done;
31062306a36Sopenharmony_ci	const struct mtk_snand_caps *caps;
31162306a36Sopenharmony_ci	struct mtk_ecc_config *ecc_cfg;
31262306a36Sopenharmony_ci	struct mtk_ecc *ecc;
31362306a36Sopenharmony_ci	struct mtk_snand_conf nfi_cfg;
31462306a36Sopenharmony_ci	struct mtk_ecc_stats ecc_stats;
31562306a36Sopenharmony_ci	struct nand_ecc_engine ecc_eng;
31662306a36Sopenharmony_ci	bool autofmt;
31762306a36Sopenharmony_ci	u8 *buf;
31862306a36Sopenharmony_ci	size_t buf_len;
31962306a36Sopenharmony_ci};
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_cistatic struct mtk_snand *nand_to_mtk_snand(struct nand_device *nand)
32262306a36Sopenharmony_ci{
32362306a36Sopenharmony_ci	struct nand_ecc_engine *eng = nand->ecc.engine;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	return container_of(eng, struct mtk_snand, ecc_eng);
32662306a36Sopenharmony_ci}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_cistatic inline int snand_prepare_bouncebuf(struct mtk_snand *snf, size_t size)
32962306a36Sopenharmony_ci{
33062306a36Sopenharmony_ci	if (snf->buf_len >= size)
33162306a36Sopenharmony_ci		return 0;
33262306a36Sopenharmony_ci	kfree(snf->buf);
33362306a36Sopenharmony_ci	snf->buf = kmalloc(size, GFP_KERNEL);
33462306a36Sopenharmony_ci	if (!snf->buf)
33562306a36Sopenharmony_ci		return -ENOMEM;
33662306a36Sopenharmony_ci	snf->buf_len = size;
33762306a36Sopenharmony_ci	memset(snf->buf, 0xff, snf->buf_len);
33862306a36Sopenharmony_ci	return 0;
33962306a36Sopenharmony_ci}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cistatic inline u32 nfi_read32(struct mtk_snand *snf, u32 reg)
34262306a36Sopenharmony_ci{
34362306a36Sopenharmony_ci	return readl(snf->nfi_base + reg);
34462306a36Sopenharmony_ci}
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_cistatic inline void nfi_write32(struct mtk_snand *snf, u32 reg, u32 val)
34762306a36Sopenharmony_ci{
34862306a36Sopenharmony_ci	writel(val, snf->nfi_base + reg);
34962306a36Sopenharmony_ci}
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_cistatic inline void nfi_write16(struct mtk_snand *snf, u32 reg, u16 val)
35262306a36Sopenharmony_ci{
35362306a36Sopenharmony_ci	writew(val, snf->nfi_base + reg);
35462306a36Sopenharmony_ci}
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_cistatic inline void nfi_rmw32(struct mtk_snand *snf, u32 reg, u32 clr, u32 set)
35762306a36Sopenharmony_ci{
35862306a36Sopenharmony_ci	u32 val;
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	val = readl(snf->nfi_base + reg);
36162306a36Sopenharmony_ci	val &= ~clr;
36262306a36Sopenharmony_ci	val |= set;
36362306a36Sopenharmony_ci	writel(val, snf->nfi_base + reg);
36462306a36Sopenharmony_ci}
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_cistatic void nfi_read_data(struct mtk_snand *snf, u32 reg, u8 *data, u32 len)
36762306a36Sopenharmony_ci{
36862306a36Sopenharmony_ci	u32 i, val = 0, es = sizeof(u32);
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	for (i = reg; i < reg + len; i++) {
37162306a36Sopenharmony_ci		if (i == reg || i % es == 0)
37262306a36Sopenharmony_ci			val = nfi_read32(snf, i & ~(es - 1));
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci		*data++ = (u8)(val >> (8 * (i % es)));
37562306a36Sopenharmony_ci	}
37662306a36Sopenharmony_ci}
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_cistatic int mtk_nfi_reset(struct mtk_snand *snf)
37962306a36Sopenharmony_ci{
38062306a36Sopenharmony_ci	u32 val, fifo_mask;
38162306a36Sopenharmony_ci	int ret;
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	nfi_write32(snf, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST);
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	ret = readw_poll_timeout(snf->nfi_base + NFI_MASTERSTA, val,
38662306a36Sopenharmony_ci				 !(val & snf->caps->mastersta_mask), 0,
38762306a36Sopenharmony_ci				 SNFI_POLL_INTERVAL);
38862306a36Sopenharmony_ci	if (ret) {
38962306a36Sopenharmony_ci		dev_err(snf->dev, "NFI master is still busy after reset\n");
39062306a36Sopenharmony_ci		return ret;
39162306a36Sopenharmony_ci	}
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	ret = readl_poll_timeout(snf->nfi_base + NFI_STA, val,
39462306a36Sopenharmony_ci				 !(val & (NFI_FSM | snf->caps->nandfsm_mask)), 0,
39562306a36Sopenharmony_ci				 SNFI_POLL_INTERVAL);
39662306a36Sopenharmony_ci	if (ret) {
39762306a36Sopenharmony_ci		dev_err(snf->dev, "Failed to reset NFI\n");
39862306a36Sopenharmony_ci		return ret;
39962306a36Sopenharmony_ci	}
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	fifo_mask = ((snf->caps->fifo_size - 1) << FIFO_RD_REMAIN_S) |
40262306a36Sopenharmony_ci		    ((snf->caps->fifo_size - 1) << FIFO_WR_REMAIN_S);
40362306a36Sopenharmony_ci	ret = readw_poll_timeout(snf->nfi_base + NFI_FIFOSTA, val,
40462306a36Sopenharmony_ci				 !(val & fifo_mask), 0, SNFI_POLL_INTERVAL);
40562306a36Sopenharmony_ci	if (ret) {
40662306a36Sopenharmony_ci		dev_err(snf->dev, "NFI FIFOs are not empty\n");
40762306a36Sopenharmony_ci		return ret;
40862306a36Sopenharmony_ci	}
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci	return 0;
41162306a36Sopenharmony_ci}
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_cistatic int mtk_snand_mac_reset(struct mtk_snand *snf)
41462306a36Sopenharmony_ci{
41562306a36Sopenharmony_ci	int ret;
41662306a36Sopenharmony_ci	u32 val;
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci	nfi_rmw32(snf, SNF_MISC_CTL, 0, SW_RST);
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	ret = readl_poll_timeout(snf->nfi_base + SNF_STA_CTL1, val,
42162306a36Sopenharmony_ci				 !(val & SPI_STATE), 0, SNFI_POLL_INTERVAL);
42262306a36Sopenharmony_ci	if (ret)
42362306a36Sopenharmony_ci		dev_err(snf->dev, "Failed to reset SNFI MAC\n");
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	nfi_write32(snf, SNF_MISC_CTL,
42662306a36Sopenharmony_ci		    (2 << FIFO_RD_LTC_S) | (10 << CS_DESELECT_CYC_S));
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	return ret;
42962306a36Sopenharmony_ci}
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_cistatic int mtk_snand_mac_trigger(struct mtk_snand *snf, u32 outlen, u32 inlen)
43262306a36Sopenharmony_ci{
43362306a36Sopenharmony_ci	int ret;
43462306a36Sopenharmony_ci	u32 val;
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	nfi_write32(snf, SNF_MAC_CTL, SF_MAC_EN);
43762306a36Sopenharmony_ci	nfi_write32(snf, SNF_MAC_OUTL, outlen);
43862306a36Sopenharmony_ci	nfi_write32(snf, SNF_MAC_INL, inlen);
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	nfi_write32(snf, SNF_MAC_CTL, SF_MAC_EN | SF_TRIG);
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	ret = readl_poll_timeout(snf->nfi_base + SNF_MAC_CTL, val,
44362306a36Sopenharmony_ci				 val & WIP_READY, 0, SNFI_POLL_INTERVAL);
44462306a36Sopenharmony_ci	if (ret) {
44562306a36Sopenharmony_ci		dev_err(snf->dev, "Timed out waiting for WIP_READY\n");
44662306a36Sopenharmony_ci		goto cleanup;
44762306a36Sopenharmony_ci	}
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci	ret = readl_poll_timeout(snf->nfi_base + SNF_MAC_CTL, val, !(val & WIP),
45062306a36Sopenharmony_ci				 0, SNFI_POLL_INTERVAL);
45162306a36Sopenharmony_ci	if (ret)
45262306a36Sopenharmony_ci		dev_err(snf->dev, "Timed out waiting for WIP cleared\n");
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_cicleanup:
45562306a36Sopenharmony_ci	nfi_write32(snf, SNF_MAC_CTL, 0);
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	return ret;
45862306a36Sopenharmony_ci}
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_cistatic int mtk_snand_mac_io(struct mtk_snand *snf, const struct spi_mem_op *op)
46162306a36Sopenharmony_ci{
46262306a36Sopenharmony_ci	u32 rx_len = 0;
46362306a36Sopenharmony_ci	u32 reg_offs = 0;
46462306a36Sopenharmony_ci	u32 val = 0;
46562306a36Sopenharmony_ci	const u8 *tx_buf = NULL;
46662306a36Sopenharmony_ci	u8 *rx_buf = NULL;
46762306a36Sopenharmony_ci	int i, ret;
46862306a36Sopenharmony_ci	u8 b;
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	if (op->data.dir == SPI_MEM_DATA_IN) {
47162306a36Sopenharmony_ci		rx_len = op->data.nbytes;
47262306a36Sopenharmony_ci		rx_buf = op->data.buf.in;
47362306a36Sopenharmony_ci	} else {
47462306a36Sopenharmony_ci		tx_buf = op->data.buf.out;
47562306a36Sopenharmony_ci	}
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	mtk_snand_mac_reset(snf);
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci	for (i = 0; i < op->cmd.nbytes; i++, reg_offs++) {
48062306a36Sopenharmony_ci		b = (op->cmd.opcode >> ((op->cmd.nbytes - i - 1) * 8)) & 0xff;
48162306a36Sopenharmony_ci		val |= b << (8 * (reg_offs % 4));
48262306a36Sopenharmony_ci		if (reg_offs % 4 == 3) {
48362306a36Sopenharmony_ci			nfi_write32(snf, SNF_GPRAM + reg_offs - 3, val);
48462306a36Sopenharmony_ci			val = 0;
48562306a36Sopenharmony_ci		}
48662306a36Sopenharmony_ci	}
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	for (i = 0; i < op->addr.nbytes; i++, reg_offs++) {
48962306a36Sopenharmony_ci		b = (op->addr.val >> ((op->addr.nbytes - i - 1) * 8)) & 0xff;
49062306a36Sopenharmony_ci		val |= b << (8 * (reg_offs % 4));
49162306a36Sopenharmony_ci		if (reg_offs % 4 == 3) {
49262306a36Sopenharmony_ci			nfi_write32(snf, SNF_GPRAM + reg_offs - 3, val);
49362306a36Sopenharmony_ci			val = 0;
49462306a36Sopenharmony_ci		}
49562306a36Sopenharmony_ci	}
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci	for (i = 0; i < op->dummy.nbytes; i++, reg_offs++) {
49862306a36Sopenharmony_ci		if (reg_offs % 4 == 3) {
49962306a36Sopenharmony_ci			nfi_write32(snf, SNF_GPRAM + reg_offs - 3, val);
50062306a36Sopenharmony_ci			val = 0;
50162306a36Sopenharmony_ci		}
50262306a36Sopenharmony_ci	}
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci	if (op->data.dir == SPI_MEM_DATA_OUT) {
50562306a36Sopenharmony_ci		for (i = 0; i < op->data.nbytes; i++, reg_offs++) {
50662306a36Sopenharmony_ci			val |= tx_buf[i] << (8 * (reg_offs % 4));
50762306a36Sopenharmony_ci			if (reg_offs % 4 == 3) {
50862306a36Sopenharmony_ci				nfi_write32(snf, SNF_GPRAM + reg_offs - 3, val);
50962306a36Sopenharmony_ci				val = 0;
51062306a36Sopenharmony_ci			}
51162306a36Sopenharmony_ci		}
51262306a36Sopenharmony_ci	}
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	if (reg_offs % 4)
51562306a36Sopenharmony_ci		nfi_write32(snf, SNF_GPRAM + (reg_offs & ~3), val);
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci	for (i = 0; i < reg_offs; i += 4)
51862306a36Sopenharmony_ci		dev_dbg(snf->dev, "%d: %08X", i,
51962306a36Sopenharmony_ci			nfi_read32(snf, SNF_GPRAM + i));
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	dev_dbg(snf->dev, "SNF TX: %u RX: %u", reg_offs, rx_len);
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	ret = mtk_snand_mac_trigger(snf, reg_offs, rx_len);
52462306a36Sopenharmony_ci	if (ret)
52562306a36Sopenharmony_ci		return ret;
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	if (!rx_len)
52862306a36Sopenharmony_ci		return 0;
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_ci	nfi_read_data(snf, SNF_GPRAM + reg_offs, rx_buf, rx_len);
53162306a36Sopenharmony_ci	return 0;
53262306a36Sopenharmony_ci}
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_cistatic int mtk_snand_setup_pagefmt(struct mtk_snand *snf, u32 page_size,
53562306a36Sopenharmony_ci				   u32 oob_size)
53662306a36Sopenharmony_ci{
53762306a36Sopenharmony_ci	int spare_idx = -1;
53862306a36Sopenharmony_ci	u32 spare_size, spare_size_shift, pagesize_idx;
53962306a36Sopenharmony_ci	u32 sector_size_512;
54062306a36Sopenharmony_ci	u8 nsectors;
54162306a36Sopenharmony_ci	int i;
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	// skip if it's already configured as required.
54462306a36Sopenharmony_ci	if (snf->nfi_cfg.page_size == page_size &&
54562306a36Sopenharmony_ci	    snf->nfi_cfg.oob_size == oob_size)
54662306a36Sopenharmony_ci		return 0;
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	nsectors = page_size / snf->caps->sector_size;
54962306a36Sopenharmony_ci	if (nsectors > snf->caps->max_sectors) {
55062306a36Sopenharmony_ci		dev_err(snf->dev, "too many sectors required.\n");
55162306a36Sopenharmony_ci		goto err;
55262306a36Sopenharmony_ci	}
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_ci	if (snf->caps->sector_size == 512) {
55562306a36Sopenharmony_ci		sector_size_512 = NFI_SEC_SEL_512;
55662306a36Sopenharmony_ci		spare_size_shift = NFI_SPARE_SIZE_S;
55762306a36Sopenharmony_ci	} else {
55862306a36Sopenharmony_ci		sector_size_512 = 0;
55962306a36Sopenharmony_ci		spare_size_shift = NFI_SPARE_SIZE_LS_S;
56062306a36Sopenharmony_ci	}
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_ci	switch (page_size) {
56362306a36Sopenharmony_ci	case SZ_512:
56462306a36Sopenharmony_ci		pagesize_idx = NFI_PAGE_SIZE_512_2K;
56562306a36Sopenharmony_ci		break;
56662306a36Sopenharmony_ci	case SZ_2K:
56762306a36Sopenharmony_ci		if (snf->caps->sector_size == 512)
56862306a36Sopenharmony_ci			pagesize_idx = NFI_PAGE_SIZE_2K_4K;
56962306a36Sopenharmony_ci		else
57062306a36Sopenharmony_ci			pagesize_idx = NFI_PAGE_SIZE_512_2K;
57162306a36Sopenharmony_ci		break;
57262306a36Sopenharmony_ci	case SZ_4K:
57362306a36Sopenharmony_ci		if (snf->caps->sector_size == 512)
57462306a36Sopenharmony_ci			pagesize_idx = NFI_PAGE_SIZE_4K_8K;
57562306a36Sopenharmony_ci		else
57662306a36Sopenharmony_ci			pagesize_idx = NFI_PAGE_SIZE_2K_4K;
57762306a36Sopenharmony_ci		break;
57862306a36Sopenharmony_ci	case SZ_8K:
57962306a36Sopenharmony_ci		if (snf->caps->sector_size == 512)
58062306a36Sopenharmony_ci			pagesize_idx = NFI_PAGE_SIZE_8K_16K;
58162306a36Sopenharmony_ci		else
58262306a36Sopenharmony_ci			pagesize_idx = NFI_PAGE_SIZE_4K_8K;
58362306a36Sopenharmony_ci		break;
58462306a36Sopenharmony_ci	case SZ_16K:
58562306a36Sopenharmony_ci		pagesize_idx = NFI_PAGE_SIZE_8K_16K;
58662306a36Sopenharmony_ci		break;
58762306a36Sopenharmony_ci	default:
58862306a36Sopenharmony_ci		dev_err(snf->dev, "unsupported page size.\n");
58962306a36Sopenharmony_ci		goto err;
59062306a36Sopenharmony_ci	}
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	spare_size = oob_size / nsectors;
59362306a36Sopenharmony_ci	// If we're using the 1KB sector size, HW will automatically double the
59462306a36Sopenharmony_ci	// spare size. We should only use half of the value in this case.
59562306a36Sopenharmony_ci	if (snf->caps->sector_size == 1024)
59662306a36Sopenharmony_ci		spare_size /= 2;
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	for (i = snf->caps->num_spare_size - 1; i >= 0; i--) {
59962306a36Sopenharmony_ci		if (snf->caps->spare_sizes[i] <= spare_size) {
60062306a36Sopenharmony_ci			spare_size = snf->caps->spare_sizes[i];
60162306a36Sopenharmony_ci			if (snf->caps->sector_size == 1024)
60262306a36Sopenharmony_ci				spare_size *= 2;
60362306a36Sopenharmony_ci			spare_idx = i;
60462306a36Sopenharmony_ci			break;
60562306a36Sopenharmony_ci		}
60662306a36Sopenharmony_ci	}
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_ci	if (spare_idx < 0) {
60962306a36Sopenharmony_ci		dev_err(snf->dev, "unsupported spare size: %u\n", spare_size);
61062306a36Sopenharmony_ci		goto err;
61162306a36Sopenharmony_ci	}
61262306a36Sopenharmony_ci
61362306a36Sopenharmony_ci	nfi_write32(snf, NFI_PAGEFMT,
61462306a36Sopenharmony_ci		    (snf->caps->fdm_ecc_size << NFI_FDM_ECC_NUM_S) |
61562306a36Sopenharmony_ci			    (snf->caps->fdm_size << NFI_FDM_NUM_S) |
61662306a36Sopenharmony_ci			    (spare_idx << spare_size_shift) |
61762306a36Sopenharmony_ci			    (pagesize_idx << NFI_PAGE_SIZE_S) |
61862306a36Sopenharmony_ci			    sector_size_512);
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci	snf->nfi_cfg.page_size = page_size;
62162306a36Sopenharmony_ci	snf->nfi_cfg.oob_size = oob_size;
62262306a36Sopenharmony_ci	snf->nfi_cfg.nsectors = nsectors;
62362306a36Sopenharmony_ci	snf->nfi_cfg.spare_size = spare_size;
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci	dev_dbg(snf->dev, "page format: (%u + %u) * %u\n",
62662306a36Sopenharmony_ci		snf->caps->sector_size, spare_size, nsectors);
62762306a36Sopenharmony_ci	return snand_prepare_bouncebuf(snf, page_size + oob_size);
62862306a36Sopenharmony_cierr:
62962306a36Sopenharmony_ci	dev_err(snf->dev, "page size %u + %u is not supported\n", page_size,
63062306a36Sopenharmony_ci		oob_size);
63162306a36Sopenharmony_ci	return -EOPNOTSUPP;
63262306a36Sopenharmony_ci}
63362306a36Sopenharmony_ci
63462306a36Sopenharmony_cistatic int mtk_snand_ooblayout_ecc(struct mtd_info *mtd, int section,
63562306a36Sopenharmony_ci				   struct mtd_oob_region *oobecc)
63662306a36Sopenharmony_ci{
63762306a36Sopenharmony_ci	// ECC area is not accessible
63862306a36Sopenharmony_ci	return -ERANGE;
63962306a36Sopenharmony_ci}
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_cistatic int mtk_snand_ooblayout_free(struct mtd_info *mtd, int section,
64262306a36Sopenharmony_ci				    struct mtd_oob_region *oobfree)
64362306a36Sopenharmony_ci{
64462306a36Sopenharmony_ci	struct nand_device *nand = mtd_to_nanddev(mtd);
64562306a36Sopenharmony_ci	struct mtk_snand *ms = nand_to_mtk_snand(nand);
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_ci	if (section >= ms->nfi_cfg.nsectors)
64862306a36Sopenharmony_ci		return -ERANGE;
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_ci	oobfree->length = ms->caps->fdm_size - 1;
65162306a36Sopenharmony_ci	oobfree->offset = section * ms->caps->fdm_size + 1;
65262306a36Sopenharmony_ci	return 0;
65362306a36Sopenharmony_ci}
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_cistatic const struct mtd_ooblayout_ops mtk_snand_ooblayout = {
65662306a36Sopenharmony_ci	.ecc = mtk_snand_ooblayout_ecc,
65762306a36Sopenharmony_ci	.free = mtk_snand_ooblayout_free,
65862306a36Sopenharmony_ci};
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_cistatic int mtk_snand_ecc_init_ctx(struct nand_device *nand)
66162306a36Sopenharmony_ci{
66262306a36Sopenharmony_ci	struct mtk_snand *snf = nand_to_mtk_snand(nand);
66362306a36Sopenharmony_ci	struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
66462306a36Sopenharmony_ci	struct nand_ecc_props *reqs = &nand->ecc.requirements;
66562306a36Sopenharmony_ci	struct nand_ecc_props *user = &nand->ecc.user_conf;
66662306a36Sopenharmony_ci	struct mtd_info *mtd = nanddev_to_mtd(nand);
66762306a36Sopenharmony_ci	int step_size = 0, strength = 0, desired_correction = 0, steps;
66862306a36Sopenharmony_ci	bool ecc_user = false;
66962306a36Sopenharmony_ci	int ret;
67062306a36Sopenharmony_ci	u32 parity_bits, max_ecc_bytes;
67162306a36Sopenharmony_ci	struct mtk_ecc_config *ecc_cfg;
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_ci	ret = mtk_snand_setup_pagefmt(snf, nand->memorg.pagesize,
67462306a36Sopenharmony_ci				      nand->memorg.oobsize);
67562306a36Sopenharmony_ci	if (ret)
67662306a36Sopenharmony_ci		return ret;
67762306a36Sopenharmony_ci
67862306a36Sopenharmony_ci	ecc_cfg = kzalloc(sizeof(*ecc_cfg), GFP_KERNEL);
67962306a36Sopenharmony_ci	if (!ecc_cfg)
68062306a36Sopenharmony_ci		return -ENOMEM;
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_ci	nand->ecc.ctx.priv = ecc_cfg;
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_ci	if (user->step_size && user->strength) {
68562306a36Sopenharmony_ci		step_size = user->step_size;
68662306a36Sopenharmony_ci		strength = user->strength;
68762306a36Sopenharmony_ci		ecc_user = true;
68862306a36Sopenharmony_ci	} else if (reqs->step_size && reqs->strength) {
68962306a36Sopenharmony_ci		step_size = reqs->step_size;
69062306a36Sopenharmony_ci		strength = reqs->strength;
69162306a36Sopenharmony_ci	}
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	if (step_size && strength) {
69462306a36Sopenharmony_ci		steps = mtd->writesize / step_size;
69562306a36Sopenharmony_ci		desired_correction = steps * strength;
69662306a36Sopenharmony_ci		strength = desired_correction / snf->nfi_cfg.nsectors;
69762306a36Sopenharmony_ci	}
69862306a36Sopenharmony_ci
69962306a36Sopenharmony_ci	ecc_cfg->mode = ECC_NFI_MODE;
70062306a36Sopenharmony_ci	ecc_cfg->sectors = snf->nfi_cfg.nsectors;
70162306a36Sopenharmony_ci	ecc_cfg->len = snf->caps->sector_size + snf->caps->fdm_ecc_size;
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_ci	// calculate the max possible strength under current page format
70462306a36Sopenharmony_ci	parity_bits = mtk_ecc_get_parity_bits(snf->ecc);
70562306a36Sopenharmony_ci	max_ecc_bytes = snf->nfi_cfg.spare_size - snf->caps->fdm_size;
70662306a36Sopenharmony_ci	ecc_cfg->strength = max_ecc_bytes * 8 / parity_bits;
70762306a36Sopenharmony_ci	mtk_ecc_adjust_strength(snf->ecc, &ecc_cfg->strength);
70862306a36Sopenharmony_ci
70962306a36Sopenharmony_ci	// if there's a user requested strength, find the minimum strength that
71062306a36Sopenharmony_ci	// meets the requirement. Otherwise use the maximum strength which is
71162306a36Sopenharmony_ci	// expected by BootROM.
71262306a36Sopenharmony_ci	if (ecc_user && strength) {
71362306a36Sopenharmony_ci		u32 s_next = ecc_cfg->strength - 1;
71462306a36Sopenharmony_ci
71562306a36Sopenharmony_ci		while (1) {
71662306a36Sopenharmony_ci			mtk_ecc_adjust_strength(snf->ecc, &s_next);
71762306a36Sopenharmony_ci			if (s_next >= ecc_cfg->strength)
71862306a36Sopenharmony_ci				break;
71962306a36Sopenharmony_ci			if (s_next < strength)
72062306a36Sopenharmony_ci				break;
72162306a36Sopenharmony_ci			s_next = ecc_cfg->strength - 1;
72262306a36Sopenharmony_ci		}
72362306a36Sopenharmony_ci	}
72462306a36Sopenharmony_ci
72562306a36Sopenharmony_ci	mtd_set_ooblayout(mtd, &mtk_snand_ooblayout);
72662306a36Sopenharmony_ci
72762306a36Sopenharmony_ci	conf->step_size = snf->caps->sector_size;
72862306a36Sopenharmony_ci	conf->strength = ecc_cfg->strength;
72962306a36Sopenharmony_ci
73062306a36Sopenharmony_ci	if (ecc_cfg->strength < strength)
73162306a36Sopenharmony_ci		dev_warn(snf->dev, "unable to fulfill ECC of %u bits.\n",
73262306a36Sopenharmony_ci			 strength);
73362306a36Sopenharmony_ci	dev_info(snf->dev, "ECC strength: %u bits per %u bytes\n",
73462306a36Sopenharmony_ci		 ecc_cfg->strength, snf->caps->sector_size);
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_ci	return 0;
73762306a36Sopenharmony_ci}
73862306a36Sopenharmony_ci
73962306a36Sopenharmony_cistatic void mtk_snand_ecc_cleanup_ctx(struct nand_device *nand)
74062306a36Sopenharmony_ci{
74162306a36Sopenharmony_ci	struct mtk_ecc_config *ecc_cfg = nand_to_ecc_ctx(nand);
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_ci	kfree(ecc_cfg);
74462306a36Sopenharmony_ci}
74562306a36Sopenharmony_ci
74662306a36Sopenharmony_cistatic int mtk_snand_ecc_prepare_io_req(struct nand_device *nand,
74762306a36Sopenharmony_ci					struct nand_page_io_req *req)
74862306a36Sopenharmony_ci{
74962306a36Sopenharmony_ci	struct mtk_snand *snf = nand_to_mtk_snand(nand);
75062306a36Sopenharmony_ci	struct mtk_ecc_config *ecc_cfg = nand_to_ecc_ctx(nand);
75162306a36Sopenharmony_ci	int ret;
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci	ret = mtk_snand_setup_pagefmt(snf, nand->memorg.pagesize,
75462306a36Sopenharmony_ci				      nand->memorg.oobsize);
75562306a36Sopenharmony_ci	if (ret)
75662306a36Sopenharmony_ci		return ret;
75762306a36Sopenharmony_ci	snf->autofmt = true;
75862306a36Sopenharmony_ci	snf->ecc_cfg = ecc_cfg;
75962306a36Sopenharmony_ci	return 0;
76062306a36Sopenharmony_ci}
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_cistatic int mtk_snand_ecc_finish_io_req(struct nand_device *nand,
76362306a36Sopenharmony_ci				       struct nand_page_io_req *req)
76462306a36Sopenharmony_ci{
76562306a36Sopenharmony_ci	struct mtk_snand *snf = nand_to_mtk_snand(nand);
76662306a36Sopenharmony_ci	struct mtd_info *mtd = nanddev_to_mtd(nand);
76762306a36Sopenharmony_ci
76862306a36Sopenharmony_ci	snf->ecc_cfg = NULL;
76962306a36Sopenharmony_ci	snf->autofmt = false;
77062306a36Sopenharmony_ci	if ((req->mode == MTD_OPS_RAW) || (req->type != NAND_PAGE_READ))
77162306a36Sopenharmony_ci		return 0;
77262306a36Sopenharmony_ci
77362306a36Sopenharmony_ci	if (snf->ecc_stats.failed)
77462306a36Sopenharmony_ci		mtd->ecc_stats.failed += snf->ecc_stats.failed;
77562306a36Sopenharmony_ci	mtd->ecc_stats.corrected += snf->ecc_stats.corrected;
77662306a36Sopenharmony_ci	return snf->ecc_stats.failed ? -EBADMSG : snf->ecc_stats.bitflips;
77762306a36Sopenharmony_ci}
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_cistatic struct nand_ecc_engine_ops mtk_snfi_ecc_engine_ops = {
78062306a36Sopenharmony_ci	.init_ctx = mtk_snand_ecc_init_ctx,
78162306a36Sopenharmony_ci	.cleanup_ctx = mtk_snand_ecc_cleanup_ctx,
78262306a36Sopenharmony_ci	.prepare_io_req = mtk_snand_ecc_prepare_io_req,
78362306a36Sopenharmony_ci	.finish_io_req = mtk_snand_ecc_finish_io_req,
78462306a36Sopenharmony_ci};
78562306a36Sopenharmony_ci
78662306a36Sopenharmony_cistatic void mtk_snand_read_fdm(struct mtk_snand *snf, u8 *buf)
78762306a36Sopenharmony_ci{
78862306a36Sopenharmony_ci	u32 vall, valm;
78962306a36Sopenharmony_ci	u8 *oobptr = buf;
79062306a36Sopenharmony_ci	int i, j;
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci	for (i = 0; i < snf->nfi_cfg.nsectors; i++) {
79362306a36Sopenharmony_ci		vall = nfi_read32(snf, NFI_FDML(i));
79462306a36Sopenharmony_ci		valm = nfi_read32(snf, NFI_FDMM(i));
79562306a36Sopenharmony_ci
79662306a36Sopenharmony_ci		for (j = 0; j < snf->caps->fdm_size; j++)
79762306a36Sopenharmony_ci			oobptr[j] = (j >= 4 ? valm : vall) >> ((j % 4) * 8);
79862306a36Sopenharmony_ci
79962306a36Sopenharmony_ci		oobptr += snf->caps->fdm_size;
80062306a36Sopenharmony_ci	}
80162306a36Sopenharmony_ci}
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_cistatic void mtk_snand_write_fdm(struct mtk_snand *snf, const u8 *buf)
80462306a36Sopenharmony_ci{
80562306a36Sopenharmony_ci	u32 fdm_size = snf->caps->fdm_size;
80662306a36Sopenharmony_ci	const u8 *oobptr = buf;
80762306a36Sopenharmony_ci	u32 vall, valm;
80862306a36Sopenharmony_ci	int i, j;
80962306a36Sopenharmony_ci
81062306a36Sopenharmony_ci	for (i = 0; i < snf->nfi_cfg.nsectors; i++) {
81162306a36Sopenharmony_ci		vall = 0;
81262306a36Sopenharmony_ci		valm = 0;
81362306a36Sopenharmony_ci
81462306a36Sopenharmony_ci		for (j = 0; j < 8; j++) {
81562306a36Sopenharmony_ci			if (j < 4)
81662306a36Sopenharmony_ci				vall |= (j < fdm_size ? oobptr[j] : 0xff)
81762306a36Sopenharmony_ci					<< (j * 8);
81862306a36Sopenharmony_ci			else
81962306a36Sopenharmony_ci				valm |= (j < fdm_size ? oobptr[j] : 0xff)
82062306a36Sopenharmony_ci					<< ((j - 4) * 8);
82162306a36Sopenharmony_ci		}
82262306a36Sopenharmony_ci
82362306a36Sopenharmony_ci		nfi_write32(snf, NFI_FDML(i), vall);
82462306a36Sopenharmony_ci		nfi_write32(snf, NFI_FDMM(i), valm);
82562306a36Sopenharmony_ci
82662306a36Sopenharmony_ci		oobptr += fdm_size;
82762306a36Sopenharmony_ci	}
82862306a36Sopenharmony_ci}
82962306a36Sopenharmony_ci
83062306a36Sopenharmony_cistatic void mtk_snand_bm_swap(struct mtk_snand *snf, u8 *buf)
83162306a36Sopenharmony_ci{
83262306a36Sopenharmony_ci	u32 buf_bbm_pos, fdm_bbm_pos;
83362306a36Sopenharmony_ci
83462306a36Sopenharmony_ci	if (!snf->caps->bbm_swap || snf->nfi_cfg.nsectors == 1)
83562306a36Sopenharmony_ci		return;
83662306a36Sopenharmony_ci
83762306a36Sopenharmony_ci	// swap [pagesize] byte on nand with the first fdm byte
83862306a36Sopenharmony_ci	// in the last sector.
83962306a36Sopenharmony_ci	buf_bbm_pos = snf->nfi_cfg.page_size -
84062306a36Sopenharmony_ci		      (snf->nfi_cfg.nsectors - 1) * snf->nfi_cfg.spare_size;
84162306a36Sopenharmony_ci	fdm_bbm_pos = snf->nfi_cfg.page_size +
84262306a36Sopenharmony_ci		      (snf->nfi_cfg.nsectors - 1) * snf->caps->fdm_size;
84362306a36Sopenharmony_ci
84462306a36Sopenharmony_ci	swap(snf->buf[fdm_bbm_pos], buf[buf_bbm_pos]);
84562306a36Sopenharmony_ci}
84662306a36Sopenharmony_ci
84762306a36Sopenharmony_cistatic void mtk_snand_fdm_bm_swap(struct mtk_snand *snf)
84862306a36Sopenharmony_ci{
84962306a36Sopenharmony_ci	u32 fdm_bbm_pos1, fdm_bbm_pos2;
85062306a36Sopenharmony_ci
85162306a36Sopenharmony_ci	if (!snf->caps->bbm_swap || snf->nfi_cfg.nsectors == 1)
85262306a36Sopenharmony_ci		return;
85362306a36Sopenharmony_ci
85462306a36Sopenharmony_ci	// swap the first fdm byte in the first and the last sector.
85562306a36Sopenharmony_ci	fdm_bbm_pos1 = snf->nfi_cfg.page_size;
85662306a36Sopenharmony_ci	fdm_bbm_pos2 = snf->nfi_cfg.page_size +
85762306a36Sopenharmony_ci		       (snf->nfi_cfg.nsectors - 1) * snf->caps->fdm_size;
85862306a36Sopenharmony_ci	swap(snf->buf[fdm_bbm_pos1], snf->buf[fdm_bbm_pos2]);
85962306a36Sopenharmony_ci}
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_cistatic int mtk_snand_read_page_cache(struct mtk_snand *snf,
86262306a36Sopenharmony_ci				     const struct spi_mem_op *op)
86362306a36Sopenharmony_ci{
86462306a36Sopenharmony_ci	u8 *buf = snf->buf;
86562306a36Sopenharmony_ci	u8 *buf_fdm = buf + snf->nfi_cfg.page_size;
86662306a36Sopenharmony_ci	// the address part to be sent by the controller
86762306a36Sopenharmony_ci	u32 op_addr = op->addr.val;
86862306a36Sopenharmony_ci	// where to start copying data from bounce buffer
86962306a36Sopenharmony_ci	u32 rd_offset = 0;
87062306a36Sopenharmony_ci	u32 dummy_clk = (op->dummy.nbytes * BITS_PER_BYTE / op->dummy.buswidth);
87162306a36Sopenharmony_ci	u32 op_mode = 0;
87262306a36Sopenharmony_ci	u32 dma_len = snf->buf_len;
87362306a36Sopenharmony_ci	int ret = 0;
87462306a36Sopenharmony_ci	u32 rd_mode, rd_bytes, val;
87562306a36Sopenharmony_ci	dma_addr_t buf_dma;
87662306a36Sopenharmony_ci
87762306a36Sopenharmony_ci	if (snf->autofmt) {
87862306a36Sopenharmony_ci		u32 last_bit;
87962306a36Sopenharmony_ci		u32 mask;
88062306a36Sopenharmony_ci
88162306a36Sopenharmony_ci		dma_len = snf->nfi_cfg.page_size;
88262306a36Sopenharmony_ci		op_mode = CNFG_AUTO_FMT_EN;
88362306a36Sopenharmony_ci		if (op->data.ecc)
88462306a36Sopenharmony_ci			op_mode |= CNFG_HW_ECC_EN;
88562306a36Sopenharmony_ci		// extract the plane bit:
88662306a36Sopenharmony_ci		// Find the highest bit set in (pagesize+oobsize).
88762306a36Sopenharmony_ci		// Bits higher than that in op->addr are kept and sent over SPI
88862306a36Sopenharmony_ci		// Lower bits are used as an offset for copying data from DMA
88962306a36Sopenharmony_ci		// bounce buffer.
89062306a36Sopenharmony_ci		last_bit = fls(snf->nfi_cfg.page_size + snf->nfi_cfg.oob_size);
89162306a36Sopenharmony_ci		mask = (1 << last_bit) - 1;
89262306a36Sopenharmony_ci		rd_offset = op_addr & mask;
89362306a36Sopenharmony_ci		op_addr &= ~mask;
89462306a36Sopenharmony_ci
89562306a36Sopenharmony_ci		// check if we can dma to the caller memory
89662306a36Sopenharmony_ci		if (rd_offset == 0 && op->data.nbytes >= snf->nfi_cfg.page_size)
89762306a36Sopenharmony_ci			buf = op->data.buf.in;
89862306a36Sopenharmony_ci	}
89962306a36Sopenharmony_ci	mtk_snand_mac_reset(snf);
90062306a36Sopenharmony_ci	mtk_nfi_reset(snf);
90162306a36Sopenharmony_ci
90262306a36Sopenharmony_ci	// command and dummy cycles
90362306a36Sopenharmony_ci	nfi_write32(snf, SNF_RD_CTL2,
90462306a36Sopenharmony_ci		    (dummy_clk << DATA_READ_DUMMY_S) |
90562306a36Sopenharmony_ci			    (op->cmd.opcode << DATA_READ_CMD_S));
90662306a36Sopenharmony_ci
90762306a36Sopenharmony_ci	// read address
90862306a36Sopenharmony_ci	nfi_write32(snf, SNF_RD_CTL3, op_addr);
90962306a36Sopenharmony_ci
91062306a36Sopenharmony_ci	// Set read op_mode
91162306a36Sopenharmony_ci	if (op->data.buswidth == 4)
91262306a36Sopenharmony_ci		rd_mode = op->addr.buswidth == 4 ? DATA_READ_MODE_QUAD :
91362306a36Sopenharmony_ci						   DATA_READ_MODE_X4;
91462306a36Sopenharmony_ci	else if (op->data.buswidth == 2)
91562306a36Sopenharmony_ci		rd_mode = op->addr.buswidth == 2 ? DATA_READ_MODE_DUAL :
91662306a36Sopenharmony_ci						   DATA_READ_MODE_X2;
91762306a36Sopenharmony_ci	else
91862306a36Sopenharmony_ci		rd_mode = DATA_READ_MODE_X1;
91962306a36Sopenharmony_ci	rd_mode <<= DATA_READ_MODE_S;
92062306a36Sopenharmony_ci	nfi_rmw32(snf, SNF_MISC_CTL, DATA_READ_MODE,
92162306a36Sopenharmony_ci		  rd_mode | DATARD_CUSTOM_EN);
92262306a36Sopenharmony_ci
92362306a36Sopenharmony_ci	// Set bytes to read
92462306a36Sopenharmony_ci	rd_bytes = (snf->nfi_cfg.spare_size + snf->caps->sector_size) *
92562306a36Sopenharmony_ci		   snf->nfi_cfg.nsectors;
92662306a36Sopenharmony_ci	nfi_write32(snf, SNF_MISC_CTL2,
92762306a36Sopenharmony_ci		    (rd_bytes << PROGRAM_LOAD_BYTE_NUM_S) | rd_bytes);
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_ci	// NFI read prepare
93062306a36Sopenharmony_ci	nfi_write16(snf, NFI_CNFG,
93162306a36Sopenharmony_ci		    (CNFG_OP_MODE_CUST << CNFG_OP_MODE_S) | CNFG_DMA_BURST_EN |
93262306a36Sopenharmony_ci			    CNFG_READ_MODE | CNFG_DMA_MODE | op_mode);
93362306a36Sopenharmony_ci
93462306a36Sopenharmony_ci	nfi_write32(snf, NFI_CON, (snf->nfi_cfg.nsectors << CON_SEC_NUM_S));
93562306a36Sopenharmony_ci
93662306a36Sopenharmony_ci	buf_dma = dma_map_single(snf->dev, buf, dma_len, DMA_FROM_DEVICE);
93762306a36Sopenharmony_ci	ret = dma_mapping_error(snf->dev, buf_dma);
93862306a36Sopenharmony_ci	if (ret) {
93962306a36Sopenharmony_ci		dev_err(snf->dev, "DMA mapping failed.\n");
94062306a36Sopenharmony_ci		goto cleanup;
94162306a36Sopenharmony_ci	}
94262306a36Sopenharmony_ci	nfi_write32(snf, NFI_STRADDR, buf_dma);
94362306a36Sopenharmony_ci	if (op->data.ecc) {
94462306a36Sopenharmony_ci		snf->ecc_cfg->op = ECC_DECODE;
94562306a36Sopenharmony_ci		ret = mtk_ecc_enable(snf->ecc, snf->ecc_cfg);
94662306a36Sopenharmony_ci		if (ret)
94762306a36Sopenharmony_ci			goto cleanup_dma;
94862306a36Sopenharmony_ci	}
94962306a36Sopenharmony_ci	// Prepare for custom read interrupt
95062306a36Sopenharmony_ci	nfi_write32(snf, NFI_INTR_EN, NFI_IRQ_INTR_EN | NFI_IRQ_CUS_READ);
95162306a36Sopenharmony_ci	reinit_completion(&snf->op_done);
95262306a36Sopenharmony_ci
95362306a36Sopenharmony_ci	// Trigger NFI into custom mode
95462306a36Sopenharmony_ci	nfi_write16(snf, NFI_CMD, NFI_CMD_DUMMY_READ);
95562306a36Sopenharmony_ci
95662306a36Sopenharmony_ci	// Start DMA read
95762306a36Sopenharmony_ci	nfi_rmw32(snf, NFI_CON, 0, CON_BRD);
95862306a36Sopenharmony_ci	nfi_write16(snf, NFI_STRDATA, STR_DATA);
95962306a36Sopenharmony_ci
96062306a36Sopenharmony_ci	if (!wait_for_completion_timeout(
96162306a36Sopenharmony_ci		    &snf->op_done, usecs_to_jiffies(SNFI_POLL_INTERVAL))) {
96262306a36Sopenharmony_ci		dev_err(snf->dev, "DMA timed out for reading from cache.\n");
96362306a36Sopenharmony_ci		ret = -ETIMEDOUT;
96462306a36Sopenharmony_ci		goto cleanup;
96562306a36Sopenharmony_ci	}
96662306a36Sopenharmony_ci
96762306a36Sopenharmony_ci	// Wait for BUS_SEC_CNTR returning expected value
96862306a36Sopenharmony_ci	ret = readl_poll_timeout(snf->nfi_base + NFI_BYTELEN, val,
96962306a36Sopenharmony_ci				 BUS_SEC_CNTR(val) >= snf->nfi_cfg.nsectors, 0,
97062306a36Sopenharmony_ci				 SNFI_POLL_INTERVAL);
97162306a36Sopenharmony_ci	if (ret) {
97262306a36Sopenharmony_ci		dev_err(snf->dev, "Timed out waiting for BUS_SEC_CNTR\n");
97362306a36Sopenharmony_ci		goto cleanup2;
97462306a36Sopenharmony_ci	}
97562306a36Sopenharmony_ci
97662306a36Sopenharmony_ci	// Wait for bus becoming idle
97762306a36Sopenharmony_ci	ret = readl_poll_timeout(snf->nfi_base + NFI_MASTERSTA, val,
97862306a36Sopenharmony_ci				 !(val & snf->caps->mastersta_mask), 0,
97962306a36Sopenharmony_ci				 SNFI_POLL_INTERVAL);
98062306a36Sopenharmony_ci	if (ret) {
98162306a36Sopenharmony_ci		dev_err(snf->dev, "Timed out waiting for bus becoming idle\n");
98262306a36Sopenharmony_ci		goto cleanup2;
98362306a36Sopenharmony_ci	}
98462306a36Sopenharmony_ci
98562306a36Sopenharmony_ci	if (op->data.ecc) {
98662306a36Sopenharmony_ci		ret = mtk_ecc_wait_done(snf->ecc, ECC_DECODE);
98762306a36Sopenharmony_ci		if (ret) {
98862306a36Sopenharmony_ci			dev_err(snf->dev, "wait ecc done timeout\n");
98962306a36Sopenharmony_ci			goto cleanup2;
99062306a36Sopenharmony_ci		}
99162306a36Sopenharmony_ci		// save status before disabling ecc
99262306a36Sopenharmony_ci		mtk_ecc_get_stats(snf->ecc, &snf->ecc_stats,
99362306a36Sopenharmony_ci				  snf->nfi_cfg.nsectors);
99462306a36Sopenharmony_ci	}
99562306a36Sopenharmony_ci
99662306a36Sopenharmony_ci	dma_unmap_single(snf->dev, buf_dma, dma_len, DMA_FROM_DEVICE);
99762306a36Sopenharmony_ci
99862306a36Sopenharmony_ci	if (snf->autofmt) {
99962306a36Sopenharmony_ci		mtk_snand_read_fdm(snf, buf_fdm);
100062306a36Sopenharmony_ci		if (snf->caps->bbm_swap) {
100162306a36Sopenharmony_ci			mtk_snand_bm_swap(snf, buf);
100262306a36Sopenharmony_ci			mtk_snand_fdm_bm_swap(snf);
100362306a36Sopenharmony_ci		}
100462306a36Sopenharmony_ci	}
100562306a36Sopenharmony_ci
100662306a36Sopenharmony_ci	// copy data back
100762306a36Sopenharmony_ci	if (nfi_read32(snf, NFI_STA) & READ_EMPTY) {
100862306a36Sopenharmony_ci		memset(op->data.buf.in, 0xff, op->data.nbytes);
100962306a36Sopenharmony_ci		snf->ecc_stats.bitflips = 0;
101062306a36Sopenharmony_ci		snf->ecc_stats.failed = 0;
101162306a36Sopenharmony_ci		snf->ecc_stats.corrected = 0;
101262306a36Sopenharmony_ci	} else {
101362306a36Sopenharmony_ci		if (buf == op->data.buf.in) {
101462306a36Sopenharmony_ci			u32 cap_len = snf->buf_len - snf->nfi_cfg.page_size;
101562306a36Sopenharmony_ci			u32 req_left = op->data.nbytes - snf->nfi_cfg.page_size;
101662306a36Sopenharmony_ci
101762306a36Sopenharmony_ci			if (req_left)
101862306a36Sopenharmony_ci				memcpy(op->data.buf.in + snf->nfi_cfg.page_size,
101962306a36Sopenharmony_ci				       buf_fdm,
102062306a36Sopenharmony_ci				       cap_len < req_left ? cap_len : req_left);
102162306a36Sopenharmony_ci		} else if (rd_offset < snf->buf_len) {
102262306a36Sopenharmony_ci			u32 cap_len = snf->buf_len - rd_offset;
102362306a36Sopenharmony_ci
102462306a36Sopenharmony_ci			if (op->data.nbytes < cap_len)
102562306a36Sopenharmony_ci				cap_len = op->data.nbytes;
102662306a36Sopenharmony_ci			memcpy(op->data.buf.in, snf->buf + rd_offset, cap_len);
102762306a36Sopenharmony_ci		}
102862306a36Sopenharmony_ci	}
102962306a36Sopenharmony_cicleanup2:
103062306a36Sopenharmony_ci	if (op->data.ecc)
103162306a36Sopenharmony_ci		mtk_ecc_disable(snf->ecc);
103262306a36Sopenharmony_cicleanup_dma:
103362306a36Sopenharmony_ci	// unmap dma only if any error happens. (otherwise it's done before
103462306a36Sopenharmony_ci	// data copying)
103562306a36Sopenharmony_ci	if (ret)
103662306a36Sopenharmony_ci		dma_unmap_single(snf->dev, buf_dma, dma_len, DMA_FROM_DEVICE);
103762306a36Sopenharmony_cicleanup:
103862306a36Sopenharmony_ci	// Stop read
103962306a36Sopenharmony_ci	nfi_write32(snf, NFI_CON, 0);
104062306a36Sopenharmony_ci	nfi_write16(snf, NFI_CNFG, 0);
104162306a36Sopenharmony_ci
104262306a36Sopenharmony_ci	// Clear SNF done flag
104362306a36Sopenharmony_ci	nfi_rmw32(snf, SNF_STA_CTL1, 0, CUS_READ_DONE);
104462306a36Sopenharmony_ci	nfi_write32(snf, SNF_STA_CTL1, 0);
104562306a36Sopenharmony_ci
104662306a36Sopenharmony_ci	// Disable interrupt
104762306a36Sopenharmony_ci	nfi_read32(snf, NFI_INTR_STA);
104862306a36Sopenharmony_ci	nfi_write32(snf, NFI_INTR_EN, 0);
104962306a36Sopenharmony_ci
105062306a36Sopenharmony_ci	nfi_rmw32(snf, SNF_MISC_CTL, DATARD_CUSTOM_EN, 0);
105162306a36Sopenharmony_ci	return ret;
105262306a36Sopenharmony_ci}
105362306a36Sopenharmony_ci
105462306a36Sopenharmony_cistatic int mtk_snand_write_page_cache(struct mtk_snand *snf,
105562306a36Sopenharmony_ci				      const struct spi_mem_op *op)
105662306a36Sopenharmony_ci{
105762306a36Sopenharmony_ci	// the address part to be sent by the controller
105862306a36Sopenharmony_ci	u32 op_addr = op->addr.val;
105962306a36Sopenharmony_ci	// where to start copying data from bounce buffer
106062306a36Sopenharmony_ci	u32 wr_offset = 0;
106162306a36Sopenharmony_ci	u32 op_mode = 0;
106262306a36Sopenharmony_ci	int ret = 0;
106362306a36Sopenharmony_ci	u32 wr_mode = 0;
106462306a36Sopenharmony_ci	u32 dma_len = snf->buf_len;
106562306a36Sopenharmony_ci	u32 wr_bytes, val;
106662306a36Sopenharmony_ci	size_t cap_len;
106762306a36Sopenharmony_ci	dma_addr_t buf_dma;
106862306a36Sopenharmony_ci
106962306a36Sopenharmony_ci	if (snf->autofmt) {
107062306a36Sopenharmony_ci		u32 last_bit;
107162306a36Sopenharmony_ci		u32 mask;
107262306a36Sopenharmony_ci
107362306a36Sopenharmony_ci		dma_len = snf->nfi_cfg.page_size;
107462306a36Sopenharmony_ci		op_mode = CNFG_AUTO_FMT_EN;
107562306a36Sopenharmony_ci		if (op->data.ecc)
107662306a36Sopenharmony_ci			op_mode |= CNFG_HW_ECC_EN;
107762306a36Sopenharmony_ci
107862306a36Sopenharmony_ci		last_bit = fls(snf->nfi_cfg.page_size + snf->nfi_cfg.oob_size);
107962306a36Sopenharmony_ci		mask = (1 << last_bit) - 1;
108062306a36Sopenharmony_ci		wr_offset = op_addr & mask;
108162306a36Sopenharmony_ci		op_addr &= ~mask;
108262306a36Sopenharmony_ci	}
108362306a36Sopenharmony_ci	mtk_snand_mac_reset(snf);
108462306a36Sopenharmony_ci	mtk_nfi_reset(snf);
108562306a36Sopenharmony_ci
108662306a36Sopenharmony_ci	if (wr_offset)
108762306a36Sopenharmony_ci		memset(snf->buf, 0xff, wr_offset);
108862306a36Sopenharmony_ci
108962306a36Sopenharmony_ci	cap_len = snf->buf_len - wr_offset;
109062306a36Sopenharmony_ci	if (op->data.nbytes < cap_len)
109162306a36Sopenharmony_ci		cap_len = op->data.nbytes;
109262306a36Sopenharmony_ci	memcpy(snf->buf + wr_offset, op->data.buf.out, cap_len);
109362306a36Sopenharmony_ci	if (snf->autofmt) {
109462306a36Sopenharmony_ci		if (snf->caps->bbm_swap) {
109562306a36Sopenharmony_ci			mtk_snand_fdm_bm_swap(snf);
109662306a36Sopenharmony_ci			mtk_snand_bm_swap(snf, snf->buf);
109762306a36Sopenharmony_ci		}
109862306a36Sopenharmony_ci		mtk_snand_write_fdm(snf, snf->buf + snf->nfi_cfg.page_size);
109962306a36Sopenharmony_ci	}
110062306a36Sopenharmony_ci
110162306a36Sopenharmony_ci	// Command
110262306a36Sopenharmony_ci	nfi_write32(snf, SNF_PG_CTL1, (op->cmd.opcode << PG_LOAD_CMD_S));
110362306a36Sopenharmony_ci
110462306a36Sopenharmony_ci	// write address
110562306a36Sopenharmony_ci	nfi_write32(snf, SNF_PG_CTL2, op_addr);
110662306a36Sopenharmony_ci
110762306a36Sopenharmony_ci	// Set read op_mode
110862306a36Sopenharmony_ci	if (op->data.buswidth == 4)
110962306a36Sopenharmony_ci		wr_mode = PG_LOAD_X4_EN;
111062306a36Sopenharmony_ci
111162306a36Sopenharmony_ci	nfi_rmw32(snf, SNF_MISC_CTL, PG_LOAD_X4_EN,
111262306a36Sopenharmony_ci		  wr_mode | PG_LOAD_CUSTOM_EN);
111362306a36Sopenharmony_ci
111462306a36Sopenharmony_ci	// Set bytes to write
111562306a36Sopenharmony_ci	wr_bytes = (snf->nfi_cfg.spare_size + snf->caps->sector_size) *
111662306a36Sopenharmony_ci		   snf->nfi_cfg.nsectors;
111762306a36Sopenharmony_ci	nfi_write32(snf, SNF_MISC_CTL2,
111862306a36Sopenharmony_ci		    (wr_bytes << PROGRAM_LOAD_BYTE_NUM_S) | wr_bytes);
111962306a36Sopenharmony_ci
112062306a36Sopenharmony_ci	// NFI write prepare
112162306a36Sopenharmony_ci	nfi_write16(snf, NFI_CNFG,
112262306a36Sopenharmony_ci		    (CNFG_OP_MODE_PROGRAM << CNFG_OP_MODE_S) |
112362306a36Sopenharmony_ci			    CNFG_DMA_BURST_EN | CNFG_DMA_MODE | op_mode);
112462306a36Sopenharmony_ci
112562306a36Sopenharmony_ci	nfi_write32(snf, NFI_CON, (snf->nfi_cfg.nsectors << CON_SEC_NUM_S));
112662306a36Sopenharmony_ci	buf_dma = dma_map_single(snf->dev, snf->buf, dma_len, DMA_TO_DEVICE);
112762306a36Sopenharmony_ci	ret = dma_mapping_error(snf->dev, buf_dma);
112862306a36Sopenharmony_ci	if (ret) {
112962306a36Sopenharmony_ci		dev_err(snf->dev, "DMA mapping failed.\n");
113062306a36Sopenharmony_ci		goto cleanup;
113162306a36Sopenharmony_ci	}
113262306a36Sopenharmony_ci	nfi_write32(snf, NFI_STRADDR, buf_dma);
113362306a36Sopenharmony_ci	if (op->data.ecc) {
113462306a36Sopenharmony_ci		snf->ecc_cfg->op = ECC_ENCODE;
113562306a36Sopenharmony_ci		ret = mtk_ecc_enable(snf->ecc, snf->ecc_cfg);
113662306a36Sopenharmony_ci		if (ret)
113762306a36Sopenharmony_ci			goto cleanup_dma;
113862306a36Sopenharmony_ci	}
113962306a36Sopenharmony_ci	// Prepare for custom write interrupt
114062306a36Sopenharmony_ci	nfi_write32(snf, NFI_INTR_EN, NFI_IRQ_INTR_EN | NFI_IRQ_CUS_PG);
114162306a36Sopenharmony_ci	reinit_completion(&snf->op_done);
114262306a36Sopenharmony_ci	;
114362306a36Sopenharmony_ci
114462306a36Sopenharmony_ci	// Trigger NFI into custom mode
114562306a36Sopenharmony_ci	nfi_write16(snf, NFI_CMD, NFI_CMD_DUMMY_WRITE);
114662306a36Sopenharmony_ci
114762306a36Sopenharmony_ci	// Start DMA write
114862306a36Sopenharmony_ci	nfi_rmw32(snf, NFI_CON, 0, CON_BWR);
114962306a36Sopenharmony_ci	nfi_write16(snf, NFI_STRDATA, STR_DATA);
115062306a36Sopenharmony_ci
115162306a36Sopenharmony_ci	if (!wait_for_completion_timeout(
115262306a36Sopenharmony_ci		    &snf->op_done, usecs_to_jiffies(SNFI_POLL_INTERVAL))) {
115362306a36Sopenharmony_ci		dev_err(snf->dev, "DMA timed out for program load.\n");
115462306a36Sopenharmony_ci		ret = -ETIMEDOUT;
115562306a36Sopenharmony_ci		goto cleanup_ecc;
115662306a36Sopenharmony_ci	}
115762306a36Sopenharmony_ci
115862306a36Sopenharmony_ci	// Wait for NFI_SEC_CNTR returning expected value
115962306a36Sopenharmony_ci	ret = readl_poll_timeout(snf->nfi_base + NFI_ADDRCNTR, val,
116062306a36Sopenharmony_ci				 NFI_SEC_CNTR(val) >= snf->nfi_cfg.nsectors, 0,
116162306a36Sopenharmony_ci				 SNFI_POLL_INTERVAL);
116262306a36Sopenharmony_ci	if (ret)
116362306a36Sopenharmony_ci		dev_err(snf->dev, "Timed out waiting for NFI_SEC_CNTR\n");
116462306a36Sopenharmony_ci
116562306a36Sopenharmony_cicleanup_ecc:
116662306a36Sopenharmony_ci	if (op->data.ecc)
116762306a36Sopenharmony_ci		mtk_ecc_disable(snf->ecc);
116862306a36Sopenharmony_cicleanup_dma:
116962306a36Sopenharmony_ci	dma_unmap_single(snf->dev, buf_dma, dma_len, DMA_TO_DEVICE);
117062306a36Sopenharmony_cicleanup:
117162306a36Sopenharmony_ci	// Stop write
117262306a36Sopenharmony_ci	nfi_write32(snf, NFI_CON, 0);
117362306a36Sopenharmony_ci	nfi_write16(snf, NFI_CNFG, 0);
117462306a36Sopenharmony_ci
117562306a36Sopenharmony_ci	// Clear SNF done flag
117662306a36Sopenharmony_ci	nfi_rmw32(snf, SNF_STA_CTL1, 0, CUS_PG_DONE);
117762306a36Sopenharmony_ci	nfi_write32(snf, SNF_STA_CTL1, 0);
117862306a36Sopenharmony_ci
117962306a36Sopenharmony_ci	// Disable interrupt
118062306a36Sopenharmony_ci	nfi_read32(snf, NFI_INTR_STA);
118162306a36Sopenharmony_ci	nfi_write32(snf, NFI_INTR_EN, 0);
118262306a36Sopenharmony_ci
118362306a36Sopenharmony_ci	nfi_rmw32(snf, SNF_MISC_CTL, PG_LOAD_CUSTOM_EN, 0);
118462306a36Sopenharmony_ci
118562306a36Sopenharmony_ci	return ret;
118662306a36Sopenharmony_ci}
118762306a36Sopenharmony_ci
118862306a36Sopenharmony_ci/**
118962306a36Sopenharmony_ci * mtk_snand_is_page_ops() - check if the op is a controller supported page op.
119062306a36Sopenharmony_ci * @op spi-mem op to check
119162306a36Sopenharmony_ci *
119262306a36Sopenharmony_ci * Check whether op can be executed with read_from_cache or program_load
119362306a36Sopenharmony_ci * mode in the controller.
119462306a36Sopenharmony_ci * This controller can execute typical Read From Cache and Program Load
119562306a36Sopenharmony_ci * instructions found on SPI-NAND with 2-byte address.
119662306a36Sopenharmony_ci * DTR and cmd buswidth & nbytes should be checked before calling this.
119762306a36Sopenharmony_ci *
119862306a36Sopenharmony_ci * Return: true if the op matches the instruction template
119962306a36Sopenharmony_ci */
120062306a36Sopenharmony_cistatic bool mtk_snand_is_page_ops(const struct spi_mem_op *op)
120162306a36Sopenharmony_ci{
120262306a36Sopenharmony_ci	if (op->addr.nbytes != 2)
120362306a36Sopenharmony_ci		return false;
120462306a36Sopenharmony_ci
120562306a36Sopenharmony_ci	if (op->addr.buswidth != 1 && op->addr.buswidth != 2 &&
120662306a36Sopenharmony_ci	    op->addr.buswidth != 4)
120762306a36Sopenharmony_ci		return false;
120862306a36Sopenharmony_ci
120962306a36Sopenharmony_ci	// match read from page instructions
121062306a36Sopenharmony_ci	if (op->data.dir == SPI_MEM_DATA_IN) {
121162306a36Sopenharmony_ci		// check dummy cycle first
121262306a36Sopenharmony_ci		if (op->dummy.nbytes * BITS_PER_BYTE / op->dummy.buswidth >
121362306a36Sopenharmony_ci		    DATA_READ_MAX_DUMMY)
121462306a36Sopenharmony_ci			return false;
121562306a36Sopenharmony_ci		// quad io / quad out
121662306a36Sopenharmony_ci		if ((op->addr.buswidth == 4 || op->addr.buswidth == 1) &&
121762306a36Sopenharmony_ci		    op->data.buswidth == 4)
121862306a36Sopenharmony_ci			return true;
121962306a36Sopenharmony_ci
122062306a36Sopenharmony_ci		// dual io / dual out
122162306a36Sopenharmony_ci		if ((op->addr.buswidth == 2 || op->addr.buswidth == 1) &&
122262306a36Sopenharmony_ci		    op->data.buswidth == 2)
122362306a36Sopenharmony_ci			return true;
122462306a36Sopenharmony_ci
122562306a36Sopenharmony_ci		// standard spi
122662306a36Sopenharmony_ci		if (op->addr.buswidth == 1 && op->data.buswidth == 1)
122762306a36Sopenharmony_ci			return true;
122862306a36Sopenharmony_ci	} else if (op->data.dir == SPI_MEM_DATA_OUT) {
122962306a36Sopenharmony_ci		// check dummy cycle first
123062306a36Sopenharmony_ci		if (op->dummy.nbytes)
123162306a36Sopenharmony_ci			return false;
123262306a36Sopenharmony_ci		// program load quad out
123362306a36Sopenharmony_ci		if (op->addr.buswidth == 1 && op->data.buswidth == 4)
123462306a36Sopenharmony_ci			return true;
123562306a36Sopenharmony_ci		// standard spi
123662306a36Sopenharmony_ci		if (op->addr.buswidth == 1 && op->data.buswidth == 1)
123762306a36Sopenharmony_ci			return true;
123862306a36Sopenharmony_ci	}
123962306a36Sopenharmony_ci	return false;
124062306a36Sopenharmony_ci}
124162306a36Sopenharmony_ci
124262306a36Sopenharmony_cistatic bool mtk_snand_supports_op(struct spi_mem *mem,
124362306a36Sopenharmony_ci				  const struct spi_mem_op *op)
124462306a36Sopenharmony_ci{
124562306a36Sopenharmony_ci	if (!spi_mem_default_supports_op(mem, op))
124662306a36Sopenharmony_ci		return false;
124762306a36Sopenharmony_ci	if (op->cmd.nbytes != 1 || op->cmd.buswidth != 1)
124862306a36Sopenharmony_ci		return false;
124962306a36Sopenharmony_ci	if (mtk_snand_is_page_ops(op))
125062306a36Sopenharmony_ci		return true;
125162306a36Sopenharmony_ci	return ((op->addr.nbytes == 0 || op->addr.buswidth == 1) &&
125262306a36Sopenharmony_ci		(op->dummy.nbytes == 0 || op->dummy.buswidth == 1) &&
125362306a36Sopenharmony_ci		(op->data.nbytes == 0 || op->data.buswidth == 1));
125462306a36Sopenharmony_ci}
125562306a36Sopenharmony_ci
125662306a36Sopenharmony_cistatic int mtk_snand_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
125762306a36Sopenharmony_ci{
125862306a36Sopenharmony_ci	struct mtk_snand *ms = spi_controller_get_devdata(mem->spi->master);
125962306a36Sopenharmony_ci	// page ops transfer size must be exactly ((sector_size + spare_size) *
126062306a36Sopenharmony_ci	// nsectors). Limit the op size if the caller requests more than that.
126162306a36Sopenharmony_ci	// exec_op will read more than needed and discard the leftover if the
126262306a36Sopenharmony_ci	// caller requests less data.
126362306a36Sopenharmony_ci	if (mtk_snand_is_page_ops(op)) {
126462306a36Sopenharmony_ci		size_t l;
126562306a36Sopenharmony_ci		// skip adjust_op_size for page ops
126662306a36Sopenharmony_ci		if (ms->autofmt)
126762306a36Sopenharmony_ci			return 0;
126862306a36Sopenharmony_ci		l = ms->caps->sector_size + ms->nfi_cfg.spare_size;
126962306a36Sopenharmony_ci		l *= ms->nfi_cfg.nsectors;
127062306a36Sopenharmony_ci		if (op->data.nbytes > l)
127162306a36Sopenharmony_ci			op->data.nbytes = l;
127262306a36Sopenharmony_ci	} else {
127362306a36Sopenharmony_ci		size_t hl = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes;
127462306a36Sopenharmony_ci
127562306a36Sopenharmony_ci		if (hl >= SNF_GPRAM_SIZE)
127662306a36Sopenharmony_ci			return -EOPNOTSUPP;
127762306a36Sopenharmony_ci		if (op->data.nbytes > SNF_GPRAM_SIZE - hl)
127862306a36Sopenharmony_ci			op->data.nbytes = SNF_GPRAM_SIZE - hl;
127962306a36Sopenharmony_ci	}
128062306a36Sopenharmony_ci	return 0;
128162306a36Sopenharmony_ci}
128262306a36Sopenharmony_ci
128362306a36Sopenharmony_cistatic int mtk_snand_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
128462306a36Sopenharmony_ci{
128562306a36Sopenharmony_ci	struct mtk_snand *ms = spi_controller_get_devdata(mem->spi->master);
128662306a36Sopenharmony_ci
128762306a36Sopenharmony_ci	dev_dbg(ms->dev, "OP %02x ADDR %08llX@%d:%u DATA %d:%u", op->cmd.opcode,
128862306a36Sopenharmony_ci		op->addr.val, op->addr.buswidth, op->addr.nbytes,
128962306a36Sopenharmony_ci		op->data.buswidth, op->data.nbytes);
129062306a36Sopenharmony_ci	if (mtk_snand_is_page_ops(op)) {
129162306a36Sopenharmony_ci		if (op->data.dir == SPI_MEM_DATA_IN)
129262306a36Sopenharmony_ci			return mtk_snand_read_page_cache(ms, op);
129362306a36Sopenharmony_ci		else
129462306a36Sopenharmony_ci			return mtk_snand_write_page_cache(ms, op);
129562306a36Sopenharmony_ci	} else {
129662306a36Sopenharmony_ci		return mtk_snand_mac_io(ms, op);
129762306a36Sopenharmony_ci	}
129862306a36Sopenharmony_ci}
129962306a36Sopenharmony_ci
130062306a36Sopenharmony_cistatic const struct spi_controller_mem_ops mtk_snand_mem_ops = {
130162306a36Sopenharmony_ci	.adjust_op_size = mtk_snand_adjust_op_size,
130262306a36Sopenharmony_ci	.supports_op = mtk_snand_supports_op,
130362306a36Sopenharmony_ci	.exec_op = mtk_snand_exec_op,
130462306a36Sopenharmony_ci};
130562306a36Sopenharmony_ci
130662306a36Sopenharmony_cistatic const struct spi_controller_mem_caps mtk_snand_mem_caps = {
130762306a36Sopenharmony_ci	.ecc = true,
130862306a36Sopenharmony_ci};
130962306a36Sopenharmony_ci
131062306a36Sopenharmony_cistatic irqreturn_t mtk_snand_irq(int irq, void *id)
131162306a36Sopenharmony_ci{
131262306a36Sopenharmony_ci	struct mtk_snand *snf = id;
131362306a36Sopenharmony_ci	u32 sta, ien;
131462306a36Sopenharmony_ci
131562306a36Sopenharmony_ci	sta = nfi_read32(snf, NFI_INTR_STA);
131662306a36Sopenharmony_ci	ien = nfi_read32(snf, NFI_INTR_EN);
131762306a36Sopenharmony_ci
131862306a36Sopenharmony_ci	if (!(sta & ien))
131962306a36Sopenharmony_ci		return IRQ_NONE;
132062306a36Sopenharmony_ci
132162306a36Sopenharmony_ci	nfi_write32(snf, NFI_INTR_EN, 0);
132262306a36Sopenharmony_ci	complete(&snf->op_done);
132362306a36Sopenharmony_ci	return IRQ_HANDLED;
132462306a36Sopenharmony_ci}
132562306a36Sopenharmony_ci
132662306a36Sopenharmony_cistatic const struct of_device_id mtk_snand_ids[] = {
132762306a36Sopenharmony_ci	{ .compatible = "mediatek,mt7622-snand", .data = &mt7622_snand_caps },
132862306a36Sopenharmony_ci	{ .compatible = "mediatek,mt7629-snand", .data = &mt7629_snand_caps },
132962306a36Sopenharmony_ci	{ .compatible = "mediatek,mt7986-snand", .data = &mt7986_snand_caps },
133062306a36Sopenharmony_ci	{},
133162306a36Sopenharmony_ci};
133262306a36Sopenharmony_ci
133362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, mtk_snand_ids);
133462306a36Sopenharmony_ci
133562306a36Sopenharmony_cistatic int mtk_snand_enable_clk(struct mtk_snand *ms)
133662306a36Sopenharmony_ci{
133762306a36Sopenharmony_ci	int ret;
133862306a36Sopenharmony_ci
133962306a36Sopenharmony_ci	ret = clk_prepare_enable(ms->nfi_clk);
134062306a36Sopenharmony_ci	if (ret) {
134162306a36Sopenharmony_ci		dev_err(ms->dev, "unable to enable nfi clk\n");
134262306a36Sopenharmony_ci		return ret;
134362306a36Sopenharmony_ci	}
134462306a36Sopenharmony_ci	ret = clk_prepare_enable(ms->pad_clk);
134562306a36Sopenharmony_ci	if (ret) {
134662306a36Sopenharmony_ci		dev_err(ms->dev, "unable to enable pad clk\n");
134762306a36Sopenharmony_ci		goto err1;
134862306a36Sopenharmony_ci	}
134962306a36Sopenharmony_ci	ret = clk_prepare_enable(ms->nfi_hclk);
135062306a36Sopenharmony_ci	if (ret) {
135162306a36Sopenharmony_ci		dev_err(ms->dev, "unable to enable nfi hclk\n");
135262306a36Sopenharmony_ci		goto err2;
135362306a36Sopenharmony_ci	}
135462306a36Sopenharmony_ci
135562306a36Sopenharmony_ci	return 0;
135662306a36Sopenharmony_ci
135762306a36Sopenharmony_cierr2:
135862306a36Sopenharmony_ci	clk_disable_unprepare(ms->pad_clk);
135962306a36Sopenharmony_cierr1:
136062306a36Sopenharmony_ci	clk_disable_unprepare(ms->nfi_clk);
136162306a36Sopenharmony_ci	return ret;
136262306a36Sopenharmony_ci}
136362306a36Sopenharmony_ci
136462306a36Sopenharmony_cistatic void mtk_snand_disable_clk(struct mtk_snand *ms)
136562306a36Sopenharmony_ci{
136662306a36Sopenharmony_ci	clk_disable_unprepare(ms->nfi_hclk);
136762306a36Sopenharmony_ci	clk_disable_unprepare(ms->pad_clk);
136862306a36Sopenharmony_ci	clk_disable_unprepare(ms->nfi_clk);
136962306a36Sopenharmony_ci}
137062306a36Sopenharmony_ci
137162306a36Sopenharmony_cistatic int mtk_snand_probe(struct platform_device *pdev)
137262306a36Sopenharmony_ci{
137362306a36Sopenharmony_ci	struct device_node *np = pdev->dev.of_node;
137462306a36Sopenharmony_ci	const struct of_device_id *dev_id;
137562306a36Sopenharmony_ci	struct spi_controller *ctlr;
137662306a36Sopenharmony_ci	struct mtk_snand *ms;
137762306a36Sopenharmony_ci	unsigned long spi_freq;
137862306a36Sopenharmony_ci	u32 val = 0;
137962306a36Sopenharmony_ci	int ret;
138062306a36Sopenharmony_ci
138162306a36Sopenharmony_ci	dev_id = of_match_node(mtk_snand_ids, np);
138262306a36Sopenharmony_ci	if (!dev_id)
138362306a36Sopenharmony_ci		return -EINVAL;
138462306a36Sopenharmony_ci
138562306a36Sopenharmony_ci	ctlr = devm_spi_alloc_master(&pdev->dev, sizeof(*ms));
138662306a36Sopenharmony_ci	if (!ctlr)
138762306a36Sopenharmony_ci		return -ENOMEM;
138862306a36Sopenharmony_ci	platform_set_drvdata(pdev, ctlr);
138962306a36Sopenharmony_ci
139062306a36Sopenharmony_ci	ms = spi_controller_get_devdata(ctlr);
139162306a36Sopenharmony_ci
139262306a36Sopenharmony_ci	ms->ctlr = ctlr;
139362306a36Sopenharmony_ci	ms->caps = dev_id->data;
139462306a36Sopenharmony_ci
139562306a36Sopenharmony_ci	ms->ecc = of_mtk_ecc_get(np);
139662306a36Sopenharmony_ci	if (IS_ERR(ms->ecc))
139762306a36Sopenharmony_ci		return PTR_ERR(ms->ecc);
139862306a36Sopenharmony_ci	else if (!ms->ecc)
139962306a36Sopenharmony_ci		return -ENODEV;
140062306a36Sopenharmony_ci
140162306a36Sopenharmony_ci	ms->nfi_base = devm_platform_ioremap_resource(pdev, 0);
140262306a36Sopenharmony_ci	if (IS_ERR(ms->nfi_base)) {
140362306a36Sopenharmony_ci		ret = PTR_ERR(ms->nfi_base);
140462306a36Sopenharmony_ci		goto release_ecc;
140562306a36Sopenharmony_ci	}
140662306a36Sopenharmony_ci
140762306a36Sopenharmony_ci	ms->dev = &pdev->dev;
140862306a36Sopenharmony_ci
140962306a36Sopenharmony_ci	ms->nfi_clk = devm_clk_get(&pdev->dev, "nfi_clk");
141062306a36Sopenharmony_ci	if (IS_ERR(ms->nfi_clk)) {
141162306a36Sopenharmony_ci		ret = PTR_ERR(ms->nfi_clk);
141262306a36Sopenharmony_ci		dev_err(&pdev->dev, "unable to get nfi_clk, err = %d\n", ret);
141362306a36Sopenharmony_ci		goto release_ecc;
141462306a36Sopenharmony_ci	}
141562306a36Sopenharmony_ci
141662306a36Sopenharmony_ci	ms->pad_clk = devm_clk_get(&pdev->dev, "pad_clk");
141762306a36Sopenharmony_ci	if (IS_ERR(ms->pad_clk)) {
141862306a36Sopenharmony_ci		ret = PTR_ERR(ms->pad_clk);
141962306a36Sopenharmony_ci		dev_err(&pdev->dev, "unable to get pad_clk, err = %d\n", ret);
142062306a36Sopenharmony_ci		goto release_ecc;
142162306a36Sopenharmony_ci	}
142262306a36Sopenharmony_ci
142362306a36Sopenharmony_ci	ms->nfi_hclk = devm_clk_get_optional(&pdev->dev, "nfi_hclk");
142462306a36Sopenharmony_ci	if (IS_ERR(ms->nfi_hclk)) {
142562306a36Sopenharmony_ci		ret = PTR_ERR(ms->nfi_hclk);
142662306a36Sopenharmony_ci		dev_err(&pdev->dev, "unable to get nfi_hclk, err = %d\n", ret);
142762306a36Sopenharmony_ci		goto release_ecc;
142862306a36Sopenharmony_ci	}
142962306a36Sopenharmony_ci
143062306a36Sopenharmony_ci	ret = mtk_snand_enable_clk(ms);
143162306a36Sopenharmony_ci	if (ret)
143262306a36Sopenharmony_ci		goto release_ecc;
143362306a36Sopenharmony_ci
143462306a36Sopenharmony_ci	init_completion(&ms->op_done);
143562306a36Sopenharmony_ci
143662306a36Sopenharmony_ci	ms->irq = platform_get_irq(pdev, 0);
143762306a36Sopenharmony_ci	if (ms->irq < 0) {
143862306a36Sopenharmony_ci		ret = ms->irq;
143962306a36Sopenharmony_ci		goto disable_clk;
144062306a36Sopenharmony_ci	}
144162306a36Sopenharmony_ci	ret = devm_request_irq(ms->dev, ms->irq, mtk_snand_irq, 0x0,
144262306a36Sopenharmony_ci			       "mtk-snand", ms);
144362306a36Sopenharmony_ci	if (ret) {
144462306a36Sopenharmony_ci		dev_err(ms->dev, "failed to request snfi irq\n");
144562306a36Sopenharmony_ci		goto disable_clk;
144662306a36Sopenharmony_ci	}
144762306a36Sopenharmony_ci
144862306a36Sopenharmony_ci	ret = dma_set_mask(ms->dev, DMA_BIT_MASK(32));
144962306a36Sopenharmony_ci	if (ret) {
145062306a36Sopenharmony_ci		dev_err(ms->dev, "failed to set dma mask\n");
145162306a36Sopenharmony_ci		goto disable_clk;
145262306a36Sopenharmony_ci	}
145362306a36Sopenharmony_ci
145462306a36Sopenharmony_ci	// switch to SNFI mode
145562306a36Sopenharmony_ci	nfi_write32(ms, SNF_CFG, SPI_MODE);
145662306a36Sopenharmony_ci
145762306a36Sopenharmony_ci	ret = of_property_read_u32(np, "rx-sample-delay-ns", &val);
145862306a36Sopenharmony_ci	if (!ret)
145962306a36Sopenharmony_ci		nfi_rmw32(ms, SNF_DLY_CTL3, SFCK_SAM_DLY,
146062306a36Sopenharmony_ci			  val * SFCK_SAM_DLY_RANGE / SFCK_SAM_DLY_TOTAL);
146162306a36Sopenharmony_ci
146262306a36Sopenharmony_ci	ret = of_property_read_u32(np, "mediatek,rx-latch-latency-ns", &val);
146362306a36Sopenharmony_ci	if (!ret) {
146462306a36Sopenharmony_ci		spi_freq = clk_get_rate(ms->pad_clk);
146562306a36Sopenharmony_ci		val = DIV_ROUND_CLOSEST(val, NSEC_PER_SEC / spi_freq);
146662306a36Sopenharmony_ci		nfi_rmw32(ms, SNF_MISC_CTL, DATA_READ_LATCH_LAT,
146762306a36Sopenharmony_ci			  val << DATA_READ_LATCH_LAT_S);
146862306a36Sopenharmony_ci	}
146962306a36Sopenharmony_ci
147062306a36Sopenharmony_ci	// setup an initial page format for ops matching page_cache_op template
147162306a36Sopenharmony_ci	// before ECC is called.
147262306a36Sopenharmony_ci	ret = mtk_snand_setup_pagefmt(ms, SZ_2K, SZ_64);
147362306a36Sopenharmony_ci	if (ret) {
147462306a36Sopenharmony_ci		dev_err(ms->dev, "failed to set initial page format\n");
147562306a36Sopenharmony_ci		goto disable_clk;
147662306a36Sopenharmony_ci	}
147762306a36Sopenharmony_ci
147862306a36Sopenharmony_ci	// setup ECC engine
147962306a36Sopenharmony_ci	ms->ecc_eng.dev = &pdev->dev;
148062306a36Sopenharmony_ci	ms->ecc_eng.integration = NAND_ECC_ENGINE_INTEGRATION_PIPELINED;
148162306a36Sopenharmony_ci	ms->ecc_eng.ops = &mtk_snfi_ecc_engine_ops;
148262306a36Sopenharmony_ci	ms->ecc_eng.priv = ms;
148362306a36Sopenharmony_ci
148462306a36Sopenharmony_ci	ret = nand_ecc_register_on_host_hw_engine(&ms->ecc_eng);
148562306a36Sopenharmony_ci	if (ret) {
148662306a36Sopenharmony_ci		dev_err(&pdev->dev, "failed to register ecc engine.\n");
148762306a36Sopenharmony_ci		goto disable_clk;
148862306a36Sopenharmony_ci	}
148962306a36Sopenharmony_ci
149062306a36Sopenharmony_ci	ctlr->num_chipselect = 1;
149162306a36Sopenharmony_ci	ctlr->mem_ops = &mtk_snand_mem_ops;
149262306a36Sopenharmony_ci	ctlr->mem_caps = &mtk_snand_mem_caps;
149362306a36Sopenharmony_ci	ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
149462306a36Sopenharmony_ci	ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
149562306a36Sopenharmony_ci	ctlr->dev.of_node = pdev->dev.of_node;
149662306a36Sopenharmony_ci	ret = spi_register_controller(ctlr);
149762306a36Sopenharmony_ci	if (ret) {
149862306a36Sopenharmony_ci		dev_err(&pdev->dev, "spi_register_controller failed.\n");
149962306a36Sopenharmony_ci		goto disable_clk;
150062306a36Sopenharmony_ci	}
150162306a36Sopenharmony_ci
150262306a36Sopenharmony_ci	return 0;
150362306a36Sopenharmony_cidisable_clk:
150462306a36Sopenharmony_ci	mtk_snand_disable_clk(ms);
150562306a36Sopenharmony_cirelease_ecc:
150662306a36Sopenharmony_ci	mtk_ecc_release(ms->ecc);
150762306a36Sopenharmony_ci	return ret;
150862306a36Sopenharmony_ci}
150962306a36Sopenharmony_ci
151062306a36Sopenharmony_cistatic void mtk_snand_remove(struct platform_device *pdev)
151162306a36Sopenharmony_ci{
151262306a36Sopenharmony_ci	struct spi_controller *ctlr = platform_get_drvdata(pdev);
151362306a36Sopenharmony_ci	struct mtk_snand *ms = spi_controller_get_devdata(ctlr);
151462306a36Sopenharmony_ci
151562306a36Sopenharmony_ci	spi_unregister_controller(ctlr);
151662306a36Sopenharmony_ci	mtk_snand_disable_clk(ms);
151762306a36Sopenharmony_ci	mtk_ecc_release(ms->ecc);
151862306a36Sopenharmony_ci	kfree(ms->buf);
151962306a36Sopenharmony_ci}
152062306a36Sopenharmony_ci
152162306a36Sopenharmony_cistatic struct platform_driver mtk_snand_driver = {
152262306a36Sopenharmony_ci	.probe = mtk_snand_probe,
152362306a36Sopenharmony_ci	.remove_new = mtk_snand_remove,
152462306a36Sopenharmony_ci	.driver = {
152562306a36Sopenharmony_ci		.name = "mtk-snand",
152662306a36Sopenharmony_ci		.of_match_table = mtk_snand_ids,
152762306a36Sopenharmony_ci	},
152862306a36Sopenharmony_ci};
152962306a36Sopenharmony_ci
153062306a36Sopenharmony_cimodule_platform_driver(mtk_snand_driver);
153162306a36Sopenharmony_ci
153262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
153362306a36Sopenharmony_ciMODULE_AUTHOR("Chuanhong Guo <gch981213@gmail.com>");
153462306a36Sopenharmony_ciMODULE_DESCRIPTION("MeidaTek SPI-NAND Flash Controller Driver");
1535