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