mirror of
				git://git.openwrt.org/openwrt/openwrt.git
				synced 2025-10-30 21:44:27 -04:00 
			
		
		
		
	This patch adds a new spi-nand driver which implements the SNFI of mt7622 and mt7629. Unlike the existing snfi driver which makes use of the spi-mem framework and the spi-nand framework with modified ecc support, this driver is implemented directly on the mtd framework with other components untouched, and provides better performance, and behaves exactly the same as the nand framework. Signed-off-by: Weijie Gao <hackpascal@gmail.com>
		
			
				
	
	
		
			1863 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1863 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
 | |
| /*
 | |
|  * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
 | |
|  *
 | |
|  * Author: Weijie Gao <weijie.gao@mediatek.com>
 | |
|  */
 | |
| 
 | |
| #include "mtk-snand-def.h"
 | |
| 
 | |
| /* NFI registers */
 | |
| #define NFI_CNFG			0x000
 | |
| #define CNFG_OP_MODE_S			12
 | |
| #define   CNFG_OP_MODE_CUST		6
 | |
| #define   CNFG_OP_MODE_PROGRAM		3
 | |
| #define CNFG_AUTO_FMT_EN		BIT(9)
 | |
| #define CNFG_HW_ECC_EN			BIT(8)
 | |
| #define CNFG_DMA_BURST_EN		BIT(2)
 | |
| #define CNFG_READ_MODE			BIT(1)
 | |
| #define CNFG_DMA_MODE			BIT(0)
 | |
| 
 | |
| #define NFI_PAGEFMT			0x0004
 | |
| #define NFI_SPARE_SIZE_LS_S		16
 | |
| #define NFI_FDM_ECC_NUM_S		12
 | |
| #define NFI_FDM_NUM_S			8
 | |
| #define NFI_SPARE_SIZE_S		4
 | |
| #define NFI_SEC_SEL_512			BIT(2)
 | |
| #define NFI_PAGE_SIZE_S			0
 | |
| #define   NFI_PAGE_SIZE_512_2K		0
 | |
| #define   NFI_PAGE_SIZE_2K_4K		1
 | |
| #define   NFI_PAGE_SIZE_4K_8K		2
 | |
| #define   NFI_PAGE_SIZE_8K_16K		3
 | |
| 
 | |
| #define NFI_CON				0x008
 | |
| #define CON_SEC_NUM_S			12
 | |
| #define CON_BWR				BIT(9)
 | |
| #define CON_BRD				BIT(8)
 | |
| #define CON_NFI_RST			BIT(1)
 | |
| #define CON_FIFO_FLUSH			BIT(0)
 | |
| 
 | |
| #define NFI_INTR_EN			0x010
 | |
| #define NFI_INTR_STA			0x014
 | |
| #define NFI_IRQ_INTR_EN			BIT(31)
 | |
| #define NFI_IRQ_CUS_READ		BIT(8)
 | |
| #define NFI_IRQ_CUS_PG			BIT(7)
 | |
| 
 | |
| #define NFI_CMD				0x020
 | |
| 
 | |
| #define NFI_STRDATA			0x040
 | |
| #define STR_DATA			BIT(0)
 | |
| 
 | |
| #define NFI_STA				0x060
 | |
| #define NFI_NAND_FSM			GENMASK(28, 24)
 | |
| #define NFI_FSM				GENMASK(19, 16)
 | |
| #define READ_EMPTY			BIT(12)
 | |
| 
 | |
| #define NFI_FIFOSTA			0x064
 | |
| #define FIFO_WR_REMAIN_S		8
 | |
| #define FIFO_RD_REMAIN_S		0
 | |
| 
 | |
| #define NFI_ADDRCNTR			0x070
 | |
| #define SEC_CNTR			GENMASK(16, 12)
 | |
| #define SEC_CNTR_S			12
 | |
| #define NFI_SEC_CNTR(val)		(((val) & SEC_CNTR) >> SEC_CNTR_S)
 | |
| 
 | |
| #define NFI_STRADDR			0x080
 | |
| 
 | |
| #define NFI_BYTELEN			0x084
 | |
| #define BUS_SEC_CNTR(val)		(((val) & SEC_CNTR) >> SEC_CNTR_S)
 | |
| 
 | |
| #define NFI_FDM0L			0x0a0
 | |
| #define NFI_FDM0M			0x0a4
 | |
| #define NFI_FDML(n)			(NFI_FDM0L + (n) * 8)
 | |
| #define NFI_FDMM(n)			(NFI_FDM0M + (n) * 8)
 | |
| 
 | |
| #define NFI_DEBUG_CON1			0x220
 | |
| #define WBUF_EN				BIT(2)
 | |
| 
 | |
| #define NFI_MASTERSTA			0x224
 | |
| #define MAS_ADDR			GENMASK(11, 9)
 | |
| #define MAS_RD				GENMASK(8, 6)
 | |
| #define MAS_WR				GENMASK(5, 3)
 | |
| #define MAS_RDDLY			GENMASK(2, 0)
 | |
| #define NFI_MASTERSTA_MASK_7622		(MAS_ADDR | MAS_RD | MAS_WR | MAS_RDDLY)
 | |
| 
 | |
| /* SNFI registers */
 | |
| #define SNF_MAC_CTL			0x500
 | |
| #define MAC_XIO_SEL			BIT(4)
 | |
| #define SF_MAC_EN			BIT(3)
 | |
| #define SF_TRIG				BIT(2)
 | |
| #define WIP_READY			BIT(1)
 | |
| #define WIP				BIT(0)
 | |
| 
 | |
| #define SNF_MAC_OUTL			0x504
 | |
| #define SNF_MAC_INL			0x508
 | |
| 
 | |
| #define SNF_RD_CTL2			0x510
 | |
| #define DATA_READ_DUMMY_S		8
 | |
| #define DATA_READ_CMD_S			0
 | |
| 
 | |
| #define SNF_RD_CTL3			0x514
 | |
| 
 | |
| #define SNF_PG_CTL1			0x524
 | |
| #define PG_LOAD_CMD_S			8
 | |
| 
 | |
| #define SNF_PG_CTL2			0x528
 | |
| 
 | |
| #define SNF_MISC_CTL			0x538
 | |
| #define SW_RST				BIT(28)
 | |
| #define FIFO_RD_LTC_S			25
 | |
| #define PG_LOAD_X4_EN			BIT(20)
 | |
| #define DATA_READ_MODE_S		16
 | |
| #define DATA_READ_MODE			GENMASK(18, 16)
 | |
| #define   DATA_READ_MODE_X1		0
 | |
| #define   DATA_READ_MODE_X2		1
 | |
| #define   DATA_READ_MODE_X4		2
 | |
| #define   DATA_READ_MODE_DUAL		5
 | |
| #define   DATA_READ_MODE_QUAD		6
 | |
| #define PG_LOAD_CUSTOM_EN		BIT(7)
 | |
| #define DATARD_CUSTOM_EN		BIT(6)
 | |
| #define CS_DESELECT_CYC_S		0
 | |
| 
 | |
| #define SNF_MISC_CTL2			0x53c
 | |
| #define PROGRAM_LOAD_BYTE_NUM_S		16
 | |
| #define READ_DATA_BYTE_NUM_S		11
 | |
| 
 | |
| #define SNF_DLY_CTL3			0x548
 | |
| #define SFCK_SAM_DLY_S			0
 | |
| 
 | |
| #define SNF_STA_CTL1			0x550
 | |
| #define CUS_PG_DONE			BIT(28)
 | |
| #define CUS_READ_DONE			BIT(27)
 | |
| #define SPI_STATE_S			0
 | |
| #define SPI_STATE			GENMASK(3, 0)
 | |
| 
 | |
| #define SNF_CFG				0x55c
 | |
| #define SPI_MODE			BIT(0)
 | |
| 
 | |
| #define SNF_GPRAM			0x800
 | |
| #define SNF_GPRAM_SIZE			0xa0
 | |
| 
 | |
| #define SNFI_POLL_INTERVAL		1000000
 | |
| 
 | |
| static const uint8_t mt7622_spare_sizes[] = { 16, 26, 27, 28 };
 | |
| 
 | |
| static const struct mtk_snand_soc_data mtk_snand_socs[__SNAND_SOC_MAX] = {
 | |
| 	[SNAND_SOC_MT7622] = {
 | |
| 		.sector_size = 512,
 | |
| 		.max_sectors = 8,
 | |
| 		.fdm_size = 8,
 | |
| 		.fdm_ecc_size = 1,
 | |
| 		.fifo_size = 32,
 | |
| 		.bbm_swap = false,
 | |
| 		.empty_page_check = false,
 | |
| 		.mastersta_mask = NFI_MASTERSTA_MASK_7622,
 | |
| 		.spare_sizes = mt7622_spare_sizes,
 | |
| 		.num_spare_size = ARRAY_SIZE(mt7622_spare_sizes)
 | |
| 	},
 | |
| 	[SNAND_SOC_MT7629] = {
 | |
| 		.sector_size = 512,
 | |
| 		.max_sectors = 8,
 | |
| 		.fdm_size = 8,
 | |
| 		.fdm_ecc_size = 1,
 | |
| 		.fifo_size = 32,
 | |
| 		.bbm_swap = true,
 | |
| 		.empty_page_check = false,
 | |
| 		.mastersta_mask = NFI_MASTERSTA_MASK_7622,
 | |
| 		.spare_sizes = mt7622_spare_sizes,
 | |
| 		.num_spare_size = ARRAY_SIZE(mt7622_spare_sizes)
 | |
| 	},
 | |
| };
 | |
| 
 | |
| static inline uint32_t nfi_read32(struct mtk_snand *snf, uint32_t reg)
 | |
| {
 | |
| 	return readl(snf->nfi_base + reg);
 | |
| }
 | |
| 
 | |
| static inline void nfi_write32(struct mtk_snand *snf, uint32_t reg,
 | |
| 			       uint32_t val)
 | |
| {
 | |
| 	writel(val, snf->nfi_base + reg);
 | |
| }
 | |
| 
 | |
| static inline void nfi_write16(struct mtk_snand *snf, uint32_t reg,
 | |
| 			       uint16_t val)
 | |
| {
 | |
| 	writew(val, snf->nfi_base + reg);
 | |
| }
 | |
| 
 | |
| static inline void nfi_rmw32(struct mtk_snand *snf, uint32_t reg, uint32_t clr,
 | |
| 			     uint32_t set)
 | |
| {
 | |
| 	uint32_t val;
 | |
| 
 | |
| 	val = readl(snf->nfi_base + reg);
 | |
| 	val &= ~clr;
 | |
| 	val |= set;
 | |
| 	writel(val, snf->nfi_base + reg);
 | |
| }
 | |
| 
 | |
| static void nfi_write_data(struct mtk_snand *snf, uint32_t reg,
 | |
| 			   const uint8_t *data, uint32_t len)
 | |
| {
 | |
| 	uint32_t i, val = 0, es = sizeof(uint32_t);
 | |
| 
 | |
| 	for (i = reg; i < reg + len; i++) {
 | |
| 		val |= ((uint32_t)*data++) << (8 * (i % es));
 | |
| 
 | |
| 		if (i % es == es - 1 || i == reg + len - 1) {
 | |
| 			nfi_write32(snf, i & ~(es - 1), val);
 | |
| 			val = 0;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void nfi_read_data(struct mtk_snand *snf, uint32_t reg, uint8_t *data,
 | |
| 			  uint32_t len)
 | |
| {
 | |
| 	uint32_t i, val = 0, es = sizeof(uint32_t);
 | |
| 
 | |
| 	for (i = reg; i < reg + len; i++) {
 | |
| 		if (i == reg || i % es == 0)
 | |
| 			val = nfi_read32(snf, i & ~(es - 1));
 | |
| 
 | |
| 		*data++ = (uint8_t)(val >> (8 * (i % es)));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static inline void do_bm_swap(uint8_t *bm1, uint8_t *bm2)
 | |
| {
 | |
| 	uint8_t tmp = *bm1;
 | |
| 	*bm1 = *bm2;
 | |
| 	*bm2 = tmp;
 | |
| }
 | |
| 
 | |
| static void mtk_snand_bm_swap_raw(struct mtk_snand *snf)
 | |
| {
 | |
| 	uint32_t fdm_bbm_pos;
 | |
| 
 | |
| 	if (!snf->nfi_soc->bbm_swap || snf->ecc_steps == 1)
 | |
| 		return;
 | |
| 
 | |
| 	fdm_bbm_pos = (snf->ecc_steps - 1) * snf->raw_sector_size +
 | |
| 		      snf->nfi_soc->sector_size;
 | |
| 	do_bm_swap(&snf->page_cache[fdm_bbm_pos],
 | |
| 		   &snf->page_cache[snf->writesize]);
 | |
| }
 | |
| 
 | |
| static void mtk_snand_bm_swap(struct mtk_snand *snf)
 | |
| {
 | |
| 	uint32_t buf_bbm_pos, fdm_bbm_pos;
 | |
| 
 | |
| 	if (!snf->nfi_soc->bbm_swap || snf->ecc_steps == 1)
 | |
| 		return;
 | |
| 
 | |
| 	buf_bbm_pos = snf->writesize -
 | |
| 		      (snf->ecc_steps - 1) * snf->spare_per_sector;
 | |
| 	fdm_bbm_pos = snf->writesize +
 | |
| 		      (snf->ecc_steps - 1) * snf->nfi_soc->fdm_size;
 | |
| 	do_bm_swap(&snf->page_cache[fdm_bbm_pos],
 | |
| 		   &snf->page_cache[buf_bbm_pos]);
 | |
| }
 | |
| 
 | |
| static void mtk_snand_fdm_bm_swap_raw(struct mtk_snand *snf)
 | |
| {
 | |
| 	uint32_t fdm_bbm_pos1, fdm_bbm_pos2;
 | |
| 
 | |
| 	if (!snf->nfi_soc->bbm_swap || snf->ecc_steps == 1)
 | |
| 		return;
 | |
| 
 | |
| 	fdm_bbm_pos1 = snf->nfi_soc->sector_size;
 | |
| 	fdm_bbm_pos2 = (snf->ecc_steps - 1) * snf->raw_sector_size +
 | |
| 		       snf->nfi_soc->sector_size;
 | |
| 	do_bm_swap(&snf->page_cache[fdm_bbm_pos1],
 | |
| 		   &snf->page_cache[fdm_bbm_pos2]);
 | |
| }
 | |
| 
 | |
| static void mtk_snand_fdm_bm_swap(struct mtk_snand *snf)
 | |
| {
 | |
| 	uint32_t fdm_bbm_pos1, fdm_bbm_pos2;
 | |
| 
 | |
| 	if (!snf->nfi_soc->bbm_swap || snf->ecc_steps == 1)
 | |
| 		return;
 | |
| 
 | |
| 	fdm_bbm_pos1 = snf->writesize;
 | |
| 	fdm_bbm_pos2 = snf->writesize +
 | |
| 		       (snf->ecc_steps - 1) * snf->nfi_soc->fdm_size;
 | |
| 	do_bm_swap(&snf->page_cache[fdm_bbm_pos1],
 | |
| 		   &snf->page_cache[fdm_bbm_pos2]);
 | |
| }
 | |
| 
 | |
| static int mtk_nfi_reset(struct mtk_snand *snf)
 | |
| {
 | |
| 	uint32_t val, fifo_mask;
 | |
| 	int ret;
 | |
| 
 | |
| 	nfi_write32(snf, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST);
 | |
| 
 | |
| 	ret = read16_poll_timeout(snf->nfi_base + NFI_MASTERSTA, val,
 | |
| 				  !(val & snf->nfi_soc->mastersta_mask), 0,
 | |
| 				  SNFI_POLL_INTERVAL);
 | |
| 	if (ret) {
 | |
| 		snand_log_nfi(snf->pdev,
 | |
| 			      "NFI master is still busy after reset\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = read32_poll_timeout(snf->nfi_base + NFI_STA, val,
 | |
| 				  !(val & (NFI_FSM | NFI_NAND_FSM)), 0,
 | |
| 				  SNFI_POLL_INTERVAL);
 | |
| 	if (ret) {
 | |
| 		snand_log_nfi(snf->pdev, "Failed to reset NFI\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	fifo_mask = ((snf->nfi_soc->fifo_size - 1) << FIFO_RD_REMAIN_S) |
 | |
| 		    ((snf->nfi_soc->fifo_size - 1) << FIFO_WR_REMAIN_S);
 | |
| 	ret = read16_poll_timeout(snf->nfi_base + NFI_FIFOSTA, val,
 | |
| 				  !(val & fifo_mask), 0, SNFI_POLL_INTERVAL);
 | |
| 	if (ret) {
 | |
| 		snand_log_nfi(snf->pdev, "NFI FIFOs are not empty\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_mac_reset(struct mtk_snand *snf)
 | |
| {
 | |
| 	int ret;
 | |
| 	uint32_t val;
 | |
| 
 | |
| 	nfi_rmw32(snf, SNF_MISC_CTL, 0, SW_RST);
 | |
| 
 | |
| 	ret = read32_poll_timeout(snf->nfi_base + SNF_STA_CTL1, val,
 | |
| 				  !(val & SPI_STATE), 0, SNFI_POLL_INTERVAL);
 | |
| 	if (ret)
 | |
| 		snand_log_snfi(snf->pdev, "Failed to reset SNFI MAC\n");
 | |
| 
 | |
| 	nfi_write32(snf, SNF_MISC_CTL, (2 << FIFO_RD_LTC_S) |
 | |
| 		    (10 << CS_DESELECT_CYC_S));
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_mac_trigger(struct mtk_snand *snf, uint32_t outlen,
 | |
| 				 uint32_t inlen)
 | |
| {
 | |
| 	int ret;
 | |
| 	uint32_t val;
 | |
| 
 | |
| 	nfi_write32(snf, SNF_MAC_CTL, SF_MAC_EN);
 | |
| 	nfi_write32(snf, SNF_MAC_OUTL, outlen);
 | |
| 	nfi_write32(snf, SNF_MAC_INL, inlen);
 | |
| 
 | |
| 	nfi_write32(snf, SNF_MAC_CTL, SF_MAC_EN | SF_TRIG);
 | |
| 
 | |
| 	ret = read32_poll_timeout(snf->nfi_base + SNF_MAC_CTL, val,
 | |
| 				  val & WIP_READY, 0, SNFI_POLL_INTERVAL);
 | |
| 	if (ret) {
 | |
| 		snand_log_snfi(snf->pdev, "Timed out waiting for WIP_READY\n");
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	ret = read32_poll_timeout(snf->nfi_base + SNF_MAC_CTL, val,
 | |
| 				  !(val & WIP), 0, SNFI_POLL_INTERVAL);
 | |
| 	if (ret) {
 | |
| 		snand_log_snfi(snf->pdev,
 | |
| 			       "Timed out waiting for WIP cleared\n");
 | |
| 	}
 | |
| 
 | |
| cleanup:
 | |
| 	nfi_write32(snf, SNF_MAC_CTL, 0);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| int mtk_snand_mac_io(struct mtk_snand *snf, const uint8_t *out, uint32_t outlen,
 | |
| 		     uint8_t *in, uint32_t inlen)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	if (outlen + inlen > SNF_GPRAM_SIZE)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	mtk_snand_mac_reset(snf);
 | |
| 
 | |
| 	nfi_write_data(snf, SNF_GPRAM, out, outlen);
 | |
| 
 | |
| 	ret = mtk_snand_mac_trigger(snf, outlen, inlen);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	if (!inlen)
 | |
| 		return 0;
 | |
| 
 | |
| 	nfi_read_data(snf, SNF_GPRAM + outlen, in, inlen);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_get_feature(struct mtk_snand *snf, uint32_t addr)
 | |
| {
 | |
| 	uint8_t op[2], val;
 | |
| 	int ret;
 | |
| 
 | |
| 	op[0] = SNAND_CMD_GET_FEATURE;
 | |
| 	op[1] = (uint8_t)addr;
 | |
| 
 | |
| 	ret = mtk_snand_mac_io(snf, op, sizeof(op), &val, 1);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return val;
 | |
| }
 | |
| 
 | |
| int mtk_snand_set_feature(struct mtk_snand *snf, uint32_t addr, uint32_t val)
 | |
| {
 | |
| 	uint8_t op[3];
 | |
| 
 | |
| 	op[0] = SNAND_CMD_SET_FEATURE;
 | |
| 	op[1] = (uint8_t)addr;
 | |
| 	op[2] = (uint8_t)val;
 | |
| 
 | |
| 	return mtk_snand_mac_io(snf, op, sizeof(op), NULL, 0);
 | |
| }
 | |
| 
 | |
| static int mtk_snand_poll_status(struct mtk_snand *snf, uint32_t wait_us)
 | |
| {
 | |
| 	int val;
 | |
| 	mtk_snand_time_t time_start, tmo;
 | |
| 
 | |
| 	time_start = timer_get_ticks();
 | |
| 	tmo = timer_time_to_tick(wait_us);
 | |
| 
 | |
| 	do {
 | |
| 		val = mtk_snand_get_feature(snf, SNAND_FEATURE_STATUS_ADDR);
 | |
| 		if (!(val & SNAND_STATUS_OIP))
 | |
| 			return val & (SNAND_STATUS_ERASE_FAIL |
 | |
| 				      SNAND_STATUS_PROGRAM_FAIL);
 | |
| 	} while (!timer_is_timeout(time_start, tmo));
 | |
| 
 | |
| 	return -ETIMEDOUT;
 | |
| }
 | |
| 
 | |
| int mtk_snand_chip_reset(struct mtk_snand *snf)
 | |
| {
 | |
| 	uint8_t op = SNAND_CMD_RESET;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = mtk_snand_mac_io(snf, &op, 1, NULL, 0);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = mtk_snand_poll_status(snf, SNFI_POLL_INTERVAL);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_config_feature(struct mtk_snand *snf, uint8_t clr,
 | |
| 				    uint8_t set)
 | |
| {
 | |
| 	int val, newval;
 | |
| 	int ret;
 | |
| 
 | |
| 	val = mtk_snand_get_feature(snf, SNAND_FEATURE_CONFIG_ADDR);
 | |
| 	if (val < 0) {
 | |
| 		snand_log_chip(snf->pdev,
 | |
| 			       "Failed to get configuration feature\n");
 | |
| 		return val;
 | |
| 	}
 | |
| 
 | |
| 	newval = (val & (~clr)) | set;
 | |
| 
 | |
| 	if (newval == val)
 | |
| 		return 0;
 | |
| 
 | |
| 	ret = mtk_snand_set_feature(snf, SNAND_FEATURE_CONFIG_ADDR,
 | |
| 				    (uint8_t)newval);
 | |
| 	if (val < 0) {
 | |
| 		snand_log_chip(snf->pdev,
 | |
| 			       "Failed to set configuration feature\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	val = mtk_snand_get_feature(snf, SNAND_FEATURE_CONFIG_ADDR);
 | |
| 	if (val < 0) {
 | |
| 		snand_log_chip(snf->pdev,
 | |
| 			       "Failed to get configuration feature\n");
 | |
| 		return val;
 | |
| 	}
 | |
| 
 | |
| 	if (newval != val)
 | |
| 		return -ENOTSUPP;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_ondie_ecc_control(struct mtk_snand *snf, bool enable)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	if (enable)
 | |
| 		ret = mtk_snand_config_feature(snf, 0, SNAND_FEATURE_ECC_EN);
 | |
| 	else
 | |
| 		ret = mtk_snand_config_feature(snf, SNAND_FEATURE_ECC_EN, 0);
 | |
| 
 | |
| 	if (ret) {
 | |
| 		snand_log_chip(snf->pdev, "Failed to %s On-Die ECC engine\n",
 | |
| 			       enable ? "enable" : "disable");
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_qspi_control(struct mtk_snand *snf, bool enable)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	if (enable) {
 | |
| 		ret = mtk_snand_config_feature(snf, 0,
 | |
| 					       SNAND_FEATURE_QUAD_ENABLE);
 | |
| 	} else {
 | |
| 		ret = mtk_snand_config_feature(snf,
 | |
| 					       SNAND_FEATURE_QUAD_ENABLE, 0);
 | |
| 	}
 | |
| 
 | |
| 	if (ret) {
 | |
| 		snand_log_chip(snf->pdev, "Failed to %s quad spi\n",
 | |
| 			       enable ? "enable" : "disable");
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_unlock(struct mtk_snand *snf)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = mtk_snand_set_feature(snf, SNAND_FEATURE_PROTECT_ADDR, 0);
 | |
| 	if (ret) {
 | |
| 		snand_log_chip(snf->pdev, "Failed to set protection feature\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_write_enable(struct mtk_snand *snf)
 | |
| {
 | |
| 	uint8_t op = SNAND_CMD_WRITE_ENABLE;
 | |
| 	int ret, val;
 | |
| 
 | |
| 	ret = mtk_snand_mac_io(snf, &op, 1, NULL, 0);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	val = mtk_snand_get_feature(snf, SNAND_FEATURE_STATUS_ADDR);
 | |
| 	if (val < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	if (val & SNAND_STATUS_WEL)
 | |
| 		return 0;
 | |
| 
 | |
| 	snand_log_chip(snf->pdev, "Failed to send write-enable command\n");
 | |
| 
 | |
| 	return -ENOTSUPP;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_select_die(struct mtk_snand *snf, uint32_t dieidx)
 | |
| {
 | |
| 	if (!snf->select_die)
 | |
| 		return 0;
 | |
| 
 | |
| 	return snf->select_die(snf, dieidx);
 | |
| }
 | |
| 
 | |
| static uint64_t mtk_snand_select_die_address(struct mtk_snand *snf,
 | |
| 					     uint64_t addr)
 | |
| {
 | |
| 	uint32_t dieidx;
 | |
| 
 | |
| 	if (!snf->select_die)
 | |
| 		return addr;
 | |
| 
 | |
| 	dieidx = addr >> snf->die_shift;
 | |
| 
 | |
| 	mtk_snand_select_die(snf, dieidx);
 | |
| 
 | |
| 	return addr & snf->die_mask;
 | |
| }
 | |
| 
 | |
| static uint32_t mtk_snand_get_plane_address(struct mtk_snand *snf,
 | |
| 					    uint32_t page)
 | |
| {
 | |
| 	uint32_t pages_per_block;
 | |
| 
 | |
| 	pages_per_block = 1 << (snf->erasesize_shift - snf->writesize_shift);
 | |
| 
 | |
| 	if (page & pages_per_block)
 | |
| 		return 1 << (snf->writesize_shift + 1);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_page_op(struct mtk_snand *snf, uint32_t page, uint8_t cmd)
 | |
| {
 | |
| 	uint8_t op[4];
 | |
| 
 | |
| 	op[0] = cmd;
 | |
| 	op[1] = (page >> 16) & 0xff;
 | |
| 	op[2] = (page >> 8) & 0xff;
 | |
| 	op[3] = page & 0xff;
 | |
| 
 | |
| 	return mtk_snand_mac_io(snf, op, sizeof(op), NULL, 0);
 | |
| }
 | |
| 
 | |
| static void mtk_snand_read_fdm(struct mtk_snand *snf, uint8_t *buf)
 | |
| {
 | |
| 	uint32_t vall, valm;
 | |
| 	uint8_t *oobptr = buf;
 | |
| 	int i, j;
 | |
| 
 | |
| 	for (i = 0; i < snf->ecc_steps; i++) {
 | |
| 		vall = nfi_read32(snf, NFI_FDML(i));
 | |
| 		valm = nfi_read32(snf, NFI_FDMM(i));
 | |
| 
 | |
| 		for (j = 0; j < snf->nfi_soc->fdm_size; j++)
 | |
| 			oobptr[j] = (j >= 4 ? valm : vall) >> ((j % 4) * 8);
 | |
| 
 | |
| 		oobptr += snf->nfi_soc->fdm_size;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int mtk_snand_read_ecc_parity(struct mtk_snand *snf, uint32_t page,
 | |
| 				     uint32_t sect, uint8_t *oob)
 | |
| {
 | |
| 	uint32_t ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size;
 | |
| 	uint32_t coladdr, raw_offs, offs;
 | |
| 	uint8_t op[4];
 | |
| 
 | |
| 	if (sizeof(op) + ecc_bytes > SNF_GPRAM_SIZE) {
 | |
| 		snand_log_snfi(snf->pdev,
 | |
| 			       "ECC parity size does not fit the GPRAM\n");
 | |
| 		return -ENOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	raw_offs = sect * snf->raw_sector_size + snf->nfi_soc->sector_size +
 | |
| 		   snf->nfi_soc->fdm_size;
 | |
| 	offs = snf->ecc_steps * snf->nfi_soc->fdm_size + sect * ecc_bytes;
 | |
| 
 | |
| 	/* Column address with plane bit */
 | |
| 	coladdr = raw_offs | mtk_snand_get_plane_address(snf, page);
 | |
| 
 | |
| 	op[0] = SNAND_CMD_READ_FROM_CACHE;
 | |
| 	op[1] = (coladdr >> 8) & 0xff;
 | |
| 	op[2] = coladdr & 0xff;
 | |
| 	op[3] = 0;
 | |
| 
 | |
| 	return mtk_snand_mac_io(snf, op, sizeof(op), oob + offs, ecc_bytes);
 | |
| }
 | |
| 
 | |
| static int mtk_snand_check_ecc_result(struct mtk_snand *snf, uint32_t page)
 | |
| {
 | |
| 	uint8_t *oob = snf->page_cache + snf->writesize;
 | |
| 	int i, rc, ret = 0, max_bitflips = 0;
 | |
| 
 | |
| 	for (i = 0; i < snf->ecc_steps; i++) {
 | |
| 		if (snf->sect_bf[i] >= 0) {
 | |
| 			if (snf->sect_bf[i] > max_bitflips)
 | |
| 				max_bitflips = snf->sect_bf[i];
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		rc = mtk_snand_read_ecc_parity(snf, page, i, oob);
 | |
| 		if (rc)
 | |
| 			return rc;
 | |
| 
 | |
| 		rc = mtk_ecc_fixup_empty_sector(snf, i);
 | |
| 		if (rc < 0) {
 | |
| 			ret = -EBADMSG;
 | |
| 
 | |
| 			snand_log_ecc(snf->pdev,
 | |
| 			      "Uncorrectable bitflips in page %u sect %u\n",
 | |
| 			      page, i);
 | |
| 		} else if (rc) {
 | |
| 			snf->sect_bf[i] = rc;
 | |
| 
 | |
| 			if (snf->sect_bf[i] > max_bitflips)
 | |
| 				max_bitflips = snf->sect_bf[i];
 | |
| 
 | |
| 			snand_log_ecc(snf->pdev,
 | |
| 			      "%u bitflip%s corrected in page %u sect %u\n",
 | |
| 			      rc, rc > 1 ? "s" : "", page, i);
 | |
| 		} else {
 | |
| 			snf->sect_bf[i] = 0;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return ret ? ret : max_bitflips;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_read_cache(struct mtk_snand *snf, uint32_t page, bool raw)
 | |
| {
 | |
| 	uint32_t coladdr, rwbytes, mode, len, val;
 | |
| 	uintptr_t dma_addr;
 | |
| 	int ret;
 | |
| 
 | |
| 	/* Column address with plane bit */
 | |
| 	coladdr = mtk_snand_get_plane_address(snf, page);
 | |
| 
 | |
| 	mtk_snand_mac_reset(snf);
 | |
| 	mtk_nfi_reset(snf);
 | |
| 
 | |
| 	/* Command and dummy cycles */
 | |
| 	nfi_write32(snf, SNF_RD_CTL2,
 | |
| 		    ((uint32_t)snf->dummy_rfc << DATA_READ_DUMMY_S) |
 | |
| 		    (snf->opcode_rfc << DATA_READ_CMD_S));
 | |
| 
 | |
| 	/* Column address */
 | |
| 	nfi_write32(snf, SNF_RD_CTL3, coladdr);
 | |
| 
 | |
| 	/* Set read mode */
 | |
| 	mode = (uint32_t)snf->mode_rfc << DATA_READ_MODE_S;
 | |
| 	nfi_rmw32(snf, SNF_MISC_CTL, DATA_READ_MODE, mode | DATARD_CUSTOM_EN);
 | |
| 
 | |
| 	/* Set bytes to read */
 | |
| 	rwbytes = snf->ecc_steps * snf->raw_sector_size;
 | |
| 	nfi_write32(snf, SNF_MISC_CTL2, (rwbytes << PROGRAM_LOAD_BYTE_NUM_S) |
 | |
| 		    rwbytes);
 | |
| 
 | |
| 	/* NFI read prepare */
 | |
| 	mode = raw ? 0 : CNFG_HW_ECC_EN | CNFG_AUTO_FMT_EN;
 | |
| 	nfi_write16(snf, NFI_CNFG, (CNFG_OP_MODE_CUST << CNFG_OP_MODE_S) |
 | |
| 		    CNFG_DMA_BURST_EN | CNFG_READ_MODE | CNFG_DMA_MODE | mode);
 | |
| 
 | |
| 	nfi_write32(snf, NFI_CON, (snf->ecc_steps << CON_SEC_NUM_S));
 | |
| 
 | |
| 	/* Prepare for DMA read */
 | |
| 	len = snf->writesize + snf->oobsize;
 | |
| 	ret = dma_mem_map(snf->pdev, snf->page_cache, &dma_addr, len, false);
 | |
| 	if (ret) {
 | |
| 		snand_log_nfi(snf->pdev,
 | |
| 			      "DMA map from device failed with %d\n", ret);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	nfi_write32(snf, NFI_STRADDR, (uint32_t)dma_addr);
 | |
| 
 | |
| 	if (!raw)
 | |
| 		mtk_snand_ecc_decoder_start(snf);
 | |
| 
 | |
| 	/* Prepare for custom read interrupt */
 | |
| 	nfi_write32(snf, NFI_INTR_EN, NFI_IRQ_INTR_EN | NFI_IRQ_CUS_READ);
 | |
| 	irq_completion_init(snf->pdev);
 | |
| 
 | |
| 	/* Trigger NFI into custom mode */
 | |
| 	nfi_write16(snf, NFI_CMD, NFI_CMD_DUMMY_READ);
 | |
| 
 | |
| 	/* Start DMA read */
 | |
| 	nfi_rmw32(snf, NFI_CON, 0, CON_BRD);
 | |
| 	nfi_write16(snf, NFI_STRDATA, STR_DATA);
 | |
| 
 | |
| 	/* Wait for operation finished */
 | |
| 	ret = irq_completion_wait(snf->pdev, snf->nfi_base + SNF_STA_CTL1,
 | |
| 				  CUS_READ_DONE, SNFI_POLL_INTERVAL);
 | |
| 	if (ret) {
 | |
| 		snand_log_nfi(snf->pdev,
 | |
| 			      "DMA timed out for reading from cache\n");
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	/* Wait for BUS_SEC_CNTR returning expected value */
 | |
| 	ret = read32_poll_timeout(snf->nfi_base + NFI_BYTELEN, val,
 | |
| 				  BUS_SEC_CNTR(val) >= snf->ecc_steps,
 | |
| 				  0, SNFI_POLL_INTERVAL);
 | |
| 	if (ret) {
 | |
| 		snand_log_nfi(snf->pdev,
 | |
| 			      "Timed out waiting for BUS_SEC_CNTR\n");
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	/* Wait for bus becoming idle */
 | |
| 	ret = read32_poll_timeout(snf->nfi_base + NFI_MASTERSTA, val,
 | |
| 				  !(val & snf->nfi_soc->mastersta_mask),
 | |
| 				  0, SNFI_POLL_INTERVAL);
 | |
| 	if (ret) {
 | |
| 		snand_log_nfi(snf->pdev,
 | |
| 			      "Timed out waiting for bus becoming idle\n");
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (!raw) {
 | |
| 		ret = mtk_ecc_wait_decoder_done(snf);
 | |
| 		if (ret)
 | |
| 			goto cleanup;
 | |
| 
 | |
| 		mtk_snand_read_fdm(snf, snf->page_cache + snf->writesize);
 | |
| 
 | |
| 		mtk_ecc_check_decode_error(snf);
 | |
| 		mtk_snand_ecc_decoder_stop(snf);
 | |
| 
 | |
| 		ret = mtk_snand_check_ecc_result(snf, page);
 | |
| 	}
 | |
| 
 | |
| cleanup:
 | |
| 	/* DMA cleanup */
 | |
| 	dma_mem_unmap(snf->pdev, dma_addr, len, false);
 | |
| 
 | |
| 	/* Stop read */
 | |
| 	nfi_write32(snf, NFI_CON, 0);
 | |
| 	nfi_write16(snf, NFI_CNFG, 0);
 | |
| 
 | |
| 	/* Clear SNF done flag */
 | |
| 	nfi_rmw32(snf, SNF_STA_CTL1, 0, CUS_READ_DONE);
 | |
| 	nfi_write32(snf, SNF_STA_CTL1, 0);
 | |
| 
 | |
| 	/* Disable interrupt */
 | |
| 	nfi_read32(snf, NFI_INTR_STA);
 | |
| 	nfi_write32(snf, NFI_INTR_EN, 0);
 | |
| 
 | |
| 	nfi_rmw32(snf, SNF_MISC_CTL, DATARD_CUSTOM_EN, 0);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void mtk_snand_from_raw_page(struct mtk_snand *snf, void *buf, void *oob)
 | |
| {
 | |
| 	uint32_t i, ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size;
 | |
| 	uint8_t *eccptr = oob + snf->ecc_steps * snf->nfi_soc->fdm_size;
 | |
| 	uint8_t *bufptr = buf, *oobptr = oob, *raw_sector;
 | |
| 
 | |
| 	for (i = 0; i < snf->ecc_steps; i++) {
 | |
| 		raw_sector = snf->page_cache + i * snf->raw_sector_size;
 | |
| 
 | |
| 		if (buf) {
 | |
| 			memcpy(bufptr, raw_sector, snf->nfi_soc->sector_size);
 | |
| 			bufptr += snf->nfi_soc->sector_size;
 | |
| 		}
 | |
| 
 | |
| 		raw_sector += snf->nfi_soc->sector_size;
 | |
| 
 | |
| 		if (oob) {
 | |
| 			memcpy(oobptr, raw_sector, snf->nfi_soc->fdm_size);
 | |
| 			oobptr += snf->nfi_soc->fdm_size;
 | |
| 			raw_sector += snf->nfi_soc->fdm_size;
 | |
| 
 | |
| 			memcpy(eccptr, raw_sector, ecc_bytes);
 | |
| 			eccptr += ecc_bytes;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int mtk_snand_do_read_page(struct mtk_snand *snf, uint64_t addr,
 | |
| 				  void *buf, void *oob, bool raw, bool format)
 | |
| {
 | |
| 	uint64_t die_addr;
 | |
| 	uint32_t page;
 | |
| 	int ret;
 | |
| 
 | |
| 	die_addr = mtk_snand_select_die_address(snf, addr);
 | |
| 	page = die_addr >> snf->writesize_shift;
 | |
| 
 | |
| 	ret = mtk_snand_page_op(snf, page, SNAND_CMD_READ_TO_CACHE);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = mtk_snand_poll_status(snf, SNFI_POLL_INTERVAL);
 | |
| 	if (ret < 0) {
 | |
| 		snand_log_chip(snf->pdev, "Read to cache command timed out\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = mtk_snand_read_cache(snf, page, raw);
 | |
| 	if (ret < 0 && ret != -EBADMSG)
 | |
| 		return ret;
 | |
| 
 | |
| 	if (raw) {
 | |
| 		if (format) {
 | |
| 			mtk_snand_bm_swap_raw(snf);
 | |
| 			mtk_snand_fdm_bm_swap_raw(snf);
 | |
| 			mtk_snand_from_raw_page(snf, buf, oob);
 | |
| 		} else {
 | |
| 			if (buf)
 | |
| 				memcpy(buf, snf->page_cache, snf->writesize);
 | |
| 
 | |
| 			if (oob) {
 | |
| 				memset(oob, 0xff, snf->oobsize);
 | |
| 				memcpy(oob, snf->page_cache + snf->writesize,
 | |
| 				       snf->ecc_steps * snf->spare_per_sector);
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		mtk_snand_bm_swap(snf);
 | |
| 		mtk_snand_fdm_bm_swap(snf);
 | |
| 
 | |
| 		if (buf)
 | |
| 			memcpy(buf, snf->page_cache, snf->writesize);
 | |
| 
 | |
| 		if (oob) {
 | |
| 			memset(oob, 0xff, snf->oobsize);
 | |
| 			memcpy(oob, snf->page_cache + snf->writesize,
 | |
| 			       snf->ecc_steps * snf->nfi_soc->fdm_size);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| int mtk_snand_read_page(struct mtk_snand *snf, uint64_t addr, void *buf,
 | |
| 			void *oob, bool raw)
 | |
| {
 | |
| 	if (!snf || (!buf && !oob))
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (addr >= snf->size)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	return mtk_snand_do_read_page(snf, addr, buf, oob, raw, true);
 | |
| }
 | |
| 
 | |
| static void mtk_snand_write_fdm(struct mtk_snand *snf, const uint8_t *buf)
 | |
| {
 | |
| 	uint32_t vall, valm, fdm_size = snf->nfi_soc->fdm_size;
 | |
| 	const uint8_t *oobptr = buf;
 | |
| 	int i, j;
 | |
| 
 | |
| 	for (i = 0; i < snf->ecc_steps; i++) {
 | |
| 		vall = 0;
 | |
| 		valm = 0;
 | |
| 
 | |
| 		for (j = 0; j < 8; j++) {
 | |
| 			if (j < 4)
 | |
| 				vall |= (j < fdm_size ? oobptr[j] : 0xff)
 | |
| 						<< (j * 8);
 | |
| 			else
 | |
| 				valm |= (j < fdm_size ? oobptr[j] : 0xff)
 | |
| 						<< ((j - 4) * 8);
 | |
| 		}
 | |
| 
 | |
| 		nfi_write32(snf, NFI_FDML(i), vall);
 | |
| 		nfi_write32(snf, NFI_FDMM(i), valm);
 | |
| 
 | |
| 		oobptr += fdm_size;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int mtk_snand_program_load(struct mtk_snand *snf, uint32_t page,
 | |
| 				  bool raw)
 | |
| {
 | |
| 	uint32_t coladdr, rwbytes, mode, len, val;
 | |
| 	uintptr_t dma_addr;
 | |
| 	int ret;
 | |
| 
 | |
| 	/* Column address with plane bit */
 | |
| 	coladdr = mtk_snand_get_plane_address(snf, page);
 | |
| 
 | |
| 	mtk_snand_mac_reset(snf);
 | |
| 	mtk_nfi_reset(snf);
 | |
| 
 | |
| 	/* Write FDM registers if necessary */
 | |
| 	if (!raw)
 | |
| 		mtk_snand_write_fdm(snf, snf->page_cache + snf->writesize);
 | |
| 
 | |
| 	/* Command */
 | |
| 	nfi_write32(snf, SNF_PG_CTL1, (snf->opcode_pl << PG_LOAD_CMD_S));
 | |
| 
 | |
| 	/* Column address */
 | |
| 	nfi_write32(snf, SNF_PG_CTL2, coladdr);
 | |
| 
 | |
| 	/* Set write mode */
 | |
| 	mode = snf->mode_pl ? PG_LOAD_X4_EN : 0;
 | |
| 	nfi_rmw32(snf, SNF_MISC_CTL, PG_LOAD_X4_EN, mode | PG_LOAD_CUSTOM_EN);
 | |
| 
 | |
| 	/* Set bytes to write */
 | |
| 	rwbytes = snf->ecc_steps * snf->raw_sector_size;
 | |
| 	nfi_write32(snf, SNF_MISC_CTL2, (rwbytes << PROGRAM_LOAD_BYTE_NUM_S) |
 | |
| 		    rwbytes);
 | |
| 
 | |
| 	/* NFI write prepare */
 | |
| 	mode = raw ? 0 : CNFG_HW_ECC_EN | CNFG_AUTO_FMT_EN;
 | |
| 	nfi_write16(snf, NFI_CNFG, (CNFG_OP_MODE_PROGRAM << CNFG_OP_MODE_S) |
 | |
| 		    CNFG_DMA_BURST_EN | CNFG_DMA_MODE | mode);
 | |
| 
 | |
| 	nfi_write32(snf, NFI_CON, (snf->ecc_steps << CON_SEC_NUM_S));
 | |
| 
 | |
| 	/* Prepare for DMA write */
 | |
| 	len = snf->writesize + snf->oobsize;
 | |
| 	ret = dma_mem_map(snf->pdev, snf->page_cache, &dma_addr, len, true);
 | |
| 	if (ret) {
 | |
| 		snand_log_nfi(snf->pdev,
 | |
| 			      "DMA map to device failed with %d\n", ret);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	nfi_write32(snf, NFI_STRADDR, (uint32_t)dma_addr);
 | |
| 
 | |
| 	if (!raw)
 | |
| 		mtk_snand_ecc_encoder_start(snf);
 | |
| 
 | |
| 	/* Prepare for custom write interrupt */
 | |
| 	nfi_write32(snf, NFI_INTR_EN, NFI_IRQ_INTR_EN | NFI_IRQ_CUS_PG);
 | |
| 	irq_completion_init(snf->pdev);
 | |
| 
 | |
| 	/* Trigger NFI into custom mode */
 | |
| 	nfi_write16(snf, NFI_CMD, NFI_CMD_DUMMY_WRITE);
 | |
| 
 | |
| 	/* Start DMA write */
 | |
| 	nfi_rmw32(snf, NFI_CON, 0, CON_BWR);
 | |
| 	nfi_write16(snf, NFI_STRDATA, STR_DATA);
 | |
| 
 | |
| 	/* Wait for operation finished */
 | |
| 	ret = irq_completion_wait(snf->pdev, snf->nfi_base + SNF_STA_CTL1,
 | |
| 				  CUS_PG_DONE, SNFI_POLL_INTERVAL);
 | |
| 	if (ret) {
 | |
| 		snand_log_nfi(snf->pdev,
 | |
| 			      "DMA timed out for program load\n");
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	/* Wait for NFI_SEC_CNTR returning expected value */
 | |
| 	ret = read32_poll_timeout(snf->nfi_base + NFI_ADDRCNTR, val,
 | |
| 				  NFI_SEC_CNTR(val) >= snf->ecc_steps,
 | |
| 				  0, SNFI_POLL_INTERVAL);
 | |
| 	if (ret) {
 | |
| 		snand_log_nfi(snf->pdev,
 | |
| 			      "Timed out waiting for NFI_SEC_CNTR\n");
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (!raw)
 | |
| 		mtk_snand_ecc_encoder_stop(snf);
 | |
| 
 | |
| cleanup:
 | |
| 	/* DMA cleanup */
 | |
| 	dma_mem_unmap(snf->pdev, dma_addr, len, true);
 | |
| 
 | |
| 	/* Stop write */
 | |
| 	nfi_write32(snf, NFI_CON, 0);
 | |
| 	nfi_write16(snf, NFI_CNFG, 0);
 | |
| 
 | |
| 	/* Clear SNF done flag */
 | |
| 	nfi_rmw32(snf, SNF_STA_CTL1, 0, CUS_PG_DONE);
 | |
| 	nfi_write32(snf, SNF_STA_CTL1, 0);
 | |
| 
 | |
| 	/* Disable interrupt */
 | |
| 	nfi_read32(snf, NFI_INTR_STA);
 | |
| 	nfi_write32(snf, NFI_INTR_EN, 0);
 | |
| 
 | |
| 	nfi_rmw32(snf, SNF_MISC_CTL, PG_LOAD_CUSTOM_EN, 0);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void mtk_snand_to_raw_page(struct mtk_snand *snf,
 | |
| 				  const void *buf, const void *oob,
 | |
| 				  bool empty_ecc)
 | |
| {
 | |
| 	uint32_t i, ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size;
 | |
| 	const uint8_t *eccptr = oob + snf->ecc_steps * snf->nfi_soc->fdm_size;
 | |
| 	const uint8_t *bufptr = buf, *oobptr = oob;
 | |
| 	uint8_t *raw_sector;
 | |
| 
 | |
| 	memset(snf->page_cache, 0xff, snf->writesize + snf->oobsize);
 | |
| 	for (i = 0; i < snf->ecc_steps; i++) {
 | |
| 		raw_sector = snf->page_cache + i * snf->raw_sector_size;
 | |
| 
 | |
| 		if (buf) {
 | |
| 			memcpy(raw_sector, bufptr, snf->nfi_soc->sector_size);
 | |
| 			bufptr += snf->nfi_soc->sector_size;
 | |
| 		}
 | |
| 
 | |
| 		raw_sector += snf->nfi_soc->sector_size;
 | |
| 
 | |
| 		if (oob) {
 | |
| 			memcpy(raw_sector, oobptr, snf->nfi_soc->fdm_size);
 | |
| 			oobptr += snf->nfi_soc->fdm_size;
 | |
| 			raw_sector += snf->nfi_soc->fdm_size;
 | |
| 
 | |
| 			if (empty_ecc)
 | |
| 				memset(raw_sector, 0xff, ecc_bytes);
 | |
| 			else
 | |
| 				memcpy(raw_sector, eccptr, ecc_bytes);
 | |
| 			eccptr += ecc_bytes;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static bool mtk_snand_is_empty_page(struct mtk_snand *snf, const void *buf,
 | |
| 				    const void *oob)
 | |
| {
 | |
| 	const uint8_t *p = buf;
 | |
| 	uint32_t i, j;
 | |
| 
 | |
| 	if (buf) {
 | |
| 		for (i = 0; i < snf->writesize; i++) {
 | |
| 			if (p[i] != 0xff)
 | |
| 				return false;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (oob) {
 | |
| 		for (j = 0; j < snf->ecc_steps; j++) {
 | |
| 			p = oob + j * snf->nfi_soc->fdm_size;
 | |
| 
 | |
| 			for (i = 0; i < snf->nfi_soc->fdm_ecc_size; i++) {
 | |
| 				if (p[i] != 0xff)
 | |
| 					return false;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_do_write_page(struct mtk_snand *snf, uint64_t addr,
 | |
| 				   const void *buf, const void *oob,
 | |
| 				   bool raw, bool format)
 | |
| {
 | |
| 	uint64_t die_addr;
 | |
| 	bool empty_ecc = false;
 | |
| 	uint32_t page;
 | |
| 	int ret;
 | |
| 
 | |
| 	die_addr = mtk_snand_select_die_address(snf, addr);
 | |
| 	page = die_addr >> snf->writesize_shift;
 | |
| 
 | |
| 	if (!raw && mtk_snand_is_empty_page(snf, buf, oob)) {
 | |
| 		/*
 | |
| 		 * If the data in the page to be ecc-ed is full 0xff,
 | |
| 		 * change to raw write mode
 | |
| 		 */
 | |
| 		raw = true;
 | |
| 		format = true;
 | |
| 
 | |
| 		/* fill ecc parity code region with 0xff */
 | |
| 		empty_ecc = true;
 | |
| 	}
 | |
| 
 | |
| 	if (raw) {
 | |
| 		if (format) {
 | |
| 			mtk_snand_to_raw_page(snf, buf, oob, empty_ecc);
 | |
| 			mtk_snand_fdm_bm_swap_raw(snf);
 | |
| 			mtk_snand_bm_swap_raw(snf);
 | |
| 		} else {
 | |
| 			memset(snf->page_cache, 0xff,
 | |
| 			       snf->writesize + snf->oobsize);
 | |
| 
 | |
| 			if (buf)
 | |
| 				memcpy(snf->page_cache, buf, snf->writesize);
 | |
| 
 | |
| 			if (oob) {
 | |
| 				memcpy(snf->page_cache + snf->writesize, oob,
 | |
| 				       snf->ecc_steps * snf->spare_per_sector);
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		memset(snf->page_cache, 0xff, snf->writesize + snf->oobsize);
 | |
| 		if (buf)
 | |
| 			memcpy(snf->page_cache, buf, snf->writesize);
 | |
| 
 | |
| 		if (oob) {
 | |
| 			memcpy(snf->page_cache + snf->writesize, oob,
 | |
| 			       snf->ecc_steps * snf->nfi_soc->fdm_size);
 | |
| 		}
 | |
| 
 | |
| 		mtk_snand_fdm_bm_swap(snf);
 | |
| 		mtk_snand_bm_swap(snf);
 | |
| 	}
 | |
| 
 | |
| 	ret = mtk_snand_write_enable(snf);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = mtk_snand_program_load(snf, page, raw);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = mtk_snand_page_op(snf, page, SNAND_CMD_PROGRAM_EXECUTE);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = mtk_snand_poll_status(snf, SNFI_POLL_INTERVAL);
 | |
| 	if (ret < 0) {
 | |
| 		snand_log_chip(snf->pdev,
 | |
| 			       "Page program command timed out on page %u\n",
 | |
| 			       page);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	if (ret & SNAND_STATUS_PROGRAM_FAIL) {
 | |
| 		snand_log_chip(snf->pdev,
 | |
| 			       "Page program failed on page %u\n", page);
 | |
| 		return -EIO;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int mtk_snand_write_page(struct mtk_snand *snf, uint64_t addr, const void *buf,
 | |
| 			 const void *oob, bool raw)
 | |
| {
 | |
| 	if (!snf || (!buf && !oob))
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (addr >= snf->size)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	return mtk_snand_do_write_page(snf, addr, buf, oob, raw, true);
 | |
| }
 | |
| 
 | |
| int mtk_snand_erase_block(struct mtk_snand *snf, uint64_t addr)
 | |
| {
 | |
| 	uint64_t die_addr;
 | |
| 	uint32_t page, block;
 | |
| 	int ret;
 | |
| 
 | |
| 	if (!snf)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (addr >= snf->size)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	die_addr = mtk_snand_select_die_address(snf, addr);
 | |
| 	block = die_addr >> snf->erasesize_shift;
 | |
| 	page = block << (snf->erasesize_shift - snf->writesize_shift);
 | |
| 
 | |
| 	ret = mtk_snand_write_enable(snf);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = mtk_snand_page_op(snf, page, SNAND_CMD_BLOCK_ERASE);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = mtk_snand_poll_status(snf, SNFI_POLL_INTERVAL);
 | |
| 	if (ret < 0) {
 | |
| 		snand_log_chip(snf->pdev,
 | |
| 			       "Block erase command timed out on block %u\n",
 | |
| 			       block);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	if (ret & SNAND_STATUS_ERASE_FAIL) {
 | |
| 		snand_log_chip(snf->pdev,
 | |
| 			       "Block erase failed on block %u\n", block);
 | |
| 		return -EIO;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_block_isbad_std(struct mtk_snand *snf, uint64_t addr)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = mtk_snand_do_read_page(snf, addr, NULL, snf->buf_cache, true,
 | |
| 				     false);
 | |
| 	if (ret && ret != -EBADMSG)
 | |
| 		return ret;
 | |
| 
 | |
| 	return snf->buf_cache[0] != 0xff;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_block_isbad_mtk(struct mtk_snand *snf, uint64_t addr)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = mtk_snand_do_read_page(snf, addr, NULL, snf->buf_cache, true,
 | |
| 				     true);
 | |
| 	if (ret && ret != -EBADMSG)
 | |
| 		return ret;
 | |
| 
 | |
| 	return snf->buf_cache[0] != 0xff;
 | |
| }
 | |
| 
 | |
| int mtk_snand_block_isbad(struct mtk_snand *snf, uint64_t addr)
 | |
| {
 | |
| 	if (!snf)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (addr >= snf->size)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	addr &= ~snf->erasesize_mask;
 | |
| 
 | |
| 	if (snf->nfi_soc->bbm_swap)
 | |
| 		return mtk_snand_block_isbad_std(snf, addr);
 | |
| 
 | |
| 	return mtk_snand_block_isbad_mtk(snf, addr);
 | |
| }
 | |
| 
 | |
| static int mtk_snand_block_markbad_std(struct mtk_snand *snf, uint64_t addr)
 | |
| {
 | |
| 	/* Standard BBM position */
 | |
| 	memset(snf->buf_cache, 0xff, snf->oobsize);
 | |
| 	snf->buf_cache[0] = 0;
 | |
| 
 | |
| 	return mtk_snand_do_write_page(snf, addr, NULL, snf->buf_cache, true,
 | |
| 				       false);
 | |
| }
 | |
| 
 | |
| static int mtk_snand_block_markbad_mtk(struct mtk_snand *snf, uint64_t addr)
 | |
| {
 | |
| 	/* Write the whole page with zeros */
 | |
| 	memset(snf->buf_cache, 0, snf->writesize + snf->oobsize);
 | |
| 
 | |
| 	return mtk_snand_do_write_page(snf, addr, snf->buf_cache,
 | |
| 				       snf->buf_cache + snf->writesize, true,
 | |
| 				       true);
 | |
| }
 | |
| 
 | |
| int mtk_snand_block_markbad(struct mtk_snand *snf, uint64_t addr)
 | |
| {
 | |
| 	if (!snf)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (addr >= snf->size)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	addr &= ~snf->erasesize_mask;
 | |
| 
 | |
| 	if (snf->nfi_soc->bbm_swap)
 | |
| 		return mtk_snand_block_markbad_std(snf, addr);
 | |
| 
 | |
| 	return mtk_snand_block_markbad_mtk(snf, addr);
 | |
| }
 | |
| 
 | |
| int mtk_snand_fill_oob(struct mtk_snand *snf, uint8_t *oobraw,
 | |
| 		       const uint8_t *oobbuf, size_t ooblen)
 | |
| {
 | |
| 	size_t len = ooblen, sect_fdm_len;
 | |
| 	const uint8_t *oob = oobbuf;
 | |
| 	uint32_t step = 0;
 | |
| 
 | |
| 	if (!snf || !oobraw || !oob)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	while (len && step < snf->ecc_steps) {
 | |
| 		sect_fdm_len = snf->nfi_soc->fdm_size - 1;
 | |
| 		if (sect_fdm_len > len)
 | |
| 			sect_fdm_len = len;
 | |
| 
 | |
| 		memcpy(oobraw + step * snf->nfi_soc->fdm_size + 1, oob,
 | |
| 		       sect_fdm_len);
 | |
| 
 | |
| 		len -= sect_fdm_len;
 | |
| 		oob += sect_fdm_len;
 | |
| 		step++;
 | |
| 	}
 | |
| 
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| int mtk_snand_transfer_oob(struct mtk_snand *snf, uint8_t *oobbuf,
 | |
| 			   size_t ooblen, const uint8_t *oobraw)
 | |
| {
 | |
| 	size_t len = ooblen, sect_fdm_len;
 | |
| 	uint8_t *oob = oobbuf;
 | |
| 	uint32_t step = 0;
 | |
| 
 | |
| 	if (!snf || !oobraw || !oob)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	while (len && step < snf->ecc_steps) {
 | |
| 		sect_fdm_len = snf->nfi_soc->fdm_size - 1;
 | |
| 		if (sect_fdm_len > len)
 | |
| 			sect_fdm_len = len;
 | |
| 
 | |
| 		memcpy(oob, oobraw + step * snf->nfi_soc->fdm_size + 1,
 | |
| 		       sect_fdm_len);
 | |
| 
 | |
| 		len -= sect_fdm_len;
 | |
| 		oob += sect_fdm_len;
 | |
| 		step++;
 | |
| 	}
 | |
| 
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| int mtk_snand_read_page_auto_oob(struct mtk_snand *snf, uint64_t addr,
 | |
| 				 void *buf, void *oob, size_t ooblen,
 | |
| 				 size_t *actualooblen, bool raw)
 | |
| {
 | |
| 	int ret, oobremain;
 | |
| 
 | |
| 	if (!snf)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (!oob)
 | |
| 		return mtk_snand_read_page(snf, addr, buf, NULL, raw);
 | |
| 
 | |
| 	ret = mtk_snand_read_page(snf, addr, buf, snf->buf_cache, raw);
 | |
| 	if (ret && ret != -EBADMSG) {
 | |
| 		if (actualooblen)
 | |
| 			*actualooblen = 0;
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	oobremain = mtk_snand_transfer_oob(snf, oob, ooblen, snf->buf_cache);
 | |
| 	if (actualooblen)
 | |
| 		*actualooblen = ooblen - oobremain;
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| int mtk_snand_write_page_auto_oob(struct mtk_snand *snf, uint64_t addr,
 | |
| 				  const void *buf, const void *oob,
 | |
| 				  size_t ooblen, size_t *actualooblen, bool raw)
 | |
| {
 | |
| 	int oobremain;
 | |
| 
 | |
| 	if (!snf)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (!oob)
 | |
| 		return mtk_snand_write_page(snf, addr, buf, NULL, raw);
 | |
| 
 | |
| 	memset(snf->buf_cache, 0xff, snf->oobsize);
 | |
| 	oobremain = mtk_snand_fill_oob(snf, snf->buf_cache, oob, ooblen);
 | |
| 	if (actualooblen)
 | |
| 		*actualooblen = ooblen - oobremain;
 | |
| 
 | |
| 	return mtk_snand_write_page(snf, addr, buf, snf->buf_cache, raw);
 | |
| }
 | |
| 
 | |
| int mtk_snand_get_chip_info(struct mtk_snand *snf,
 | |
| 			    struct mtk_snand_chip_info *info)
 | |
| {
 | |
| 	if (!snf || !info)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	info->model = snf->model;
 | |
| 	info->chipsize = snf->size;
 | |
| 	info->blocksize = snf->erasesize;
 | |
| 	info->pagesize = snf->writesize;
 | |
| 	info->sparesize = snf->oobsize;
 | |
| 	info->spare_per_sector = snf->spare_per_sector;
 | |
| 	info->fdm_size = snf->nfi_soc->fdm_size;
 | |
| 	info->fdm_ecc_size = snf->nfi_soc->fdm_ecc_size;
 | |
| 	info->num_sectors = snf->ecc_steps;
 | |
| 	info->sector_size = snf->nfi_soc->sector_size;
 | |
| 	info->ecc_strength = snf->ecc_strength;
 | |
| 	info->ecc_bytes = snf->ecc_bytes;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int mtk_snand_irq_process(struct mtk_snand *snf)
 | |
| {
 | |
| 	uint32_t sta, ien;
 | |
| 
 | |
| 	if (!snf)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	sta = nfi_read32(snf, NFI_INTR_STA);
 | |
| 	ien = nfi_read32(snf, NFI_INTR_EN);
 | |
| 
 | |
| 	if (!(sta & ien))
 | |
| 		return 0;
 | |
| 
 | |
| 	nfi_write32(snf, NFI_INTR_EN, 0);
 | |
| 	irq_completion_done(snf->pdev);
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_select_spare_per_sector(struct mtk_snand *snf)
 | |
| {
 | |
| 	uint32_t spare_per_step = snf->oobsize / snf->ecc_steps;
 | |
| 	int i, mul = 1;
 | |
| 
 | |
| 	/*
 | |
| 	 * If we're using the 1KB sector size, HW will automatically
 | |
| 	 * double the spare size. So we should only use half of the value.
 | |
| 	 */
 | |
| 	if (snf->nfi_soc->sector_size == 1024)
 | |
| 		mul = 2;
 | |
| 
 | |
| 	spare_per_step /= mul;
 | |
| 
 | |
| 	for (i = snf->nfi_soc->num_spare_size - 1; i >= 0; i--) {
 | |
| 		if (snf->nfi_soc->spare_sizes[i] <= spare_per_step) {
 | |
| 			snf->spare_per_sector = snf->nfi_soc->spare_sizes[i];
 | |
| 			snf->spare_per_sector *= mul;
 | |
| 			return i;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	snand_log_nfi(snf->pdev,
 | |
| 		      "Page size %u+%u is not supported\n", snf->writesize,
 | |
| 		      snf->oobsize);
 | |
| 
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_pagefmt_setup(struct mtk_snand *snf)
 | |
| {
 | |
| 	uint32_t spare_size_idx, spare_size_shift, pagesize_idx;
 | |
| 	uint32_t sector_size_512;
 | |
| 
 | |
| 	if (snf->nfi_soc->sector_size == 512) {
 | |
| 		sector_size_512 = NFI_SEC_SEL_512;
 | |
| 		spare_size_shift = NFI_SPARE_SIZE_S;
 | |
| 	} else {
 | |
| 		sector_size_512 = 0;
 | |
| 		spare_size_shift = NFI_SPARE_SIZE_LS_S;
 | |
| 	}
 | |
| 
 | |
| 	switch (snf->writesize) {
 | |
| 	case SZ_512:
 | |
| 		pagesize_idx = NFI_PAGE_SIZE_512_2K;
 | |
| 		break;
 | |
| 	case SZ_2K:
 | |
| 		if (snf->nfi_soc->sector_size == 512)
 | |
| 			pagesize_idx = NFI_PAGE_SIZE_2K_4K;
 | |
| 		else
 | |
| 			pagesize_idx = NFI_PAGE_SIZE_512_2K;
 | |
| 		break;
 | |
| 	case SZ_4K:
 | |
| 		if (snf->nfi_soc->sector_size == 512)
 | |
| 			pagesize_idx = NFI_PAGE_SIZE_4K_8K;
 | |
| 		else
 | |
| 			pagesize_idx = NFI_PAGE_SIZE_2K_4K;
 | |
| 		break;
 | |
| 	case SZ_8K:
 | |
| 		if (snf->nfi_soc->sector_size == 512)
 | |
| 			pagesize_idx = NFI_PAGE_SIZE_8K_16K;
 | |
| 		else
 | |
| 			pagesize_idx = NFI_PAGE_SIZE_4K_8K;
 | |
| 		break;
 | |
| 	case SZ_16K:
 | |
| 		pagesize_idx = NFI_PAGE_SIZE_8K_16K;
 | |
| 		break;
 | |
| 	default:
 | |
| 		snand_log_nfi(snf->pdev, "Page size %u is not supported\n",
 | |
| 			      snf->writesize);
 | |
| 		return -ENOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	spare_size_idx = mtk_snand_select_spare_per_sector(snf);
 | |
| 	if (unlikely(spare_size_idx < 0))
 | |
| 		return -ENOTSUPP;
 | |
| 
 | |
| 	snf->raw_sector_size = snf->nfi_soc->sector_size +
 | |
| 			       snf->spare_per_sector;
 | |
| 
 | |
| 	/* Setup page format */
 | |
| 	nfi_write32(snf, NFI_PAGEFMT,
 | |
| 		    (snf->nfi_soc->fdm_ecc_size << NFI_FDM_ECC_NUM_S) |
 | |
| 		    (snf->nfi_soc->fdm_size << NFI_FDM_NUM_S) |
 | |
| 		    (spare_size_idx << spare_size_shift) |
 | |
| 		    (pagesize_idx << NFI_PAGE_SIZE_S) |
 | |
| 		    sector_size_512);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static enum snand_flash_io mtk_snand_select_opcode(struct mtk_snand *snf,
 | |
| 				   uint32_t snfi_caps, uint8_t *opcode,
 | |
| 				   uint8_t *dummy,
 | |
| 				   const struct snand_io_cap *op_cap)
 | |
| {
 | |
| 	uint32_t i, caps;
 | |
| 
 | |
| 	caps = snfi_caps & op_cap->caps;
 | |
| 
 | |
| 	i = fls(caps);
 | |
| 	if (i > 0) {
 | |
| 		*opcode = op_cap->opcodes[i - 1].opcode;
 | |
| 		if (dummy)
 | |
| 			*dummy = op_cap->opcodes[i - 1].dummy;
 | |
| 		return i - 1;
 | |
| 	}
 | |
| 
 | |
| 	return __SNAND_IO_MAX;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_select_opcode_rfc(struct mtk_snand *snf,
 | |
| 				       uint32_t snfi_caps,
 | |
| 				       const struct snand_io_cap *op_cap)
 | |
| {
 | |
| 	enum snand_flash_io idx;
 | |
| 
 | |
| 	static const uint8_t rfc_modes[__SNAND_IO_MAX] = {
 | |
| 		[SNAND_IO_1_1_1] = DATA_READ_MODE_X1,
 | |
| 		[SNAND_IO_1_1_2] = DATA_READ_MODE_X2,
 | |
| 		[SNAND_IO_1_2_2] = DATA_READ_MODE_DUAL,
 | |
| 		[SNAND_IO_1_1_4] = DATA_READ_MODE_X4,
 | |
| 		[SNAND_IO_1_4_4] = DATA_READ_MODE_QUAD,
 | |
| 	};
 | |
| 
 | |
| 	idx = mtk_snand_select_opcode(snf, snfi_caps, &snf->opcode_rfc,
 | |
| 				      &snf->dummy_rfc, op_cap);
 | |
| 	if (idx >= __SNAND_IO_MAX) {
 | |
| 		snand_log_snfi(snf->pdev,
 | |
| 			       "No capable opcode for read from cache\n");
 | |
| 		return -ENOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	snf->mode_rfc = rfc_modes[idx];
 | |
| 
 | |
| 	if (idx == SNAND_IO_1_1_4 || idx == SNAND_IO_1_4_4)
 | |
| 		snf->quad_spi_op = true;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_select_opcode_pl(struct mtk_snand *snf, uint32_t snfi_caps,
 | |
| 				      const struct snand_io_cap *op_cap)
 | |
| {
 | |
| 	enum snand_flash_io idx;
 | |
| 
 | |
| 	static const uint8_t pl_modes[__SNAND_IO_MAX] = {
 | |
| 		[SNAND_IO_1_1_1] = 0,
 | |
| 		[SNAND_IO_1_1_4] = 1,
 | |
| 	};
 | |
| 
 | |
| 	idx = mtk_snand_select_opcode(snf, snfi_caps, &snf->opcode_pl,
 | |
| 				      NULL, op_cap);
 | |
| 	if (idx >= __SNAND_IO_MAX) {
 | |
| 		snand_log_snfi(snf->pdev,
 | |
| 			       "No capable opcode for program load\n");
 | |
| 		return -ENOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	snf->mode_pl = pl_modes[idx];
 | |
| 
 | |
| 	if (idx == SNAND_IO_1_1_4)
 | |
| 		snf->quad_spi_op = true;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_setup(struct mtk_snand *snf,
 | |
| 			   const struct snand_flash_info *snand_info)
 | |
| {
 | |
| 	const struct snand_mem_org *memorg = &snand_info->memorg;
 | |
| 	uint32_t i, msg_size, snfi_caps;
 | |
| 	int ret;
 | |
| 
 | |
| 	/* Calculate flash memory organization */
 | |
| 	snf->model = snand_info->model;
 | |
| 	snf->writesize = memorg->pagesize;
 | |
| 	snf->oobsize = memorg->sparesize;
 | |
| 	snf->erasesize = snf->writesize * memorg->pages_per_block;
 | |
| 	snf->die_size = (uint64_t)snf->erasesize * memorg->blocks_per_die;
 | |
| 	snf->size = snf->die_size * memorg->ndies;
 | |
| 	snf->num_dies = memorg->ndies;
 | |
| 
 | |
| 	snf->writesize_mask = snf->writesize - 1;
 | |
| 	snf->erasesize_mask = snf->erasesize - 1;
 | |
| 	snf->die_mask = snf->die_size - 1;
 | |
| 
 | |
| 	snf->writesize_shift = ffs(snf->writesize) - 1;
 | |
| 	snf->erasesize_shift = ffs(snf->erasesize) - 1;
 | |
| 	snf->die_shift = mtk_snand_ffs64(snf->die_size) - 1;
 | |
| 
 | |
| 	snf->select_die = snand_info->select_die;
 | |
| 
 | |
| 	/* Determine opcodes for read from cache/program load */
 | |
| 	snfi_caps = SPI_IO_1_1_1 | SPI_IO_1_1_2 | SPI_IO_1_2_2;
 | |
| 	if (snf->snfi_quad_spi)
 | |
| 		snfi_caps |= SPI_IO_1_1_4 | SPI_IO_1_4_4;
 | |
| 
 | |
| 	ret = mtk_snand_select_opcode_rfc(snf, snfi_caps, snand_info->cap_rd);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = mtk_snand_select_opcode_pl(snf, snfi_caps, snand_info->cap_pl);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	/* ECC and page format */
 | |
| 	snf->ecc_steps = snf->writesize / snf->nfi_soc->sector_size;
 | |
| 	if (snf->ecc_steps > snf->nfi_soc->max_sectors) {
 | |
| 		snand_log_nfi(snf->pdev, "Page size %u is not supported\n",
 | |
| 			      snf->writesize);
 | |
| 		return -ENOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	ret = mtk_snand_pagefmt_setup(snf);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	msg_size = snf->nfi_soc->sector_size + snf->nfi_soc->fdm_ecc_size;
 | |
| 	ret = mtk_ecc_setup(snf, snf->nfi_base + NFI_FDM0L,
 | |
| 			    snf->spare_per_sector - snf->nfi_soc->fdm_size,
 | |
| 			    msg_size);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	nfi_write16(snf, NFI_CNFG, 0);
 | |
| 
 | |
| 	/* Tuning options */
 | |
| 	nfi_write16(snf, NFI_DEBUG_CON1, WBUF_EN);
 | |
| 	nfi_write32(snf, SNF_DLY_CTL3, (40 << SFCK_SAM_DLY_S));
 | |
| 
 | |
| 	/* Interrupts */
 | |
| 	nfi_read32(snf, NFI_INTR_STA);
 | |
| 	nfi_write32(snf, NFI_INTR_EN, 0);
 | |
| 
 | |
| 	/* Clear SNF done flag */
 | |
| 	nfi_rmw32(snf, SNF_STA_CTL1, 0, CUS_READ_DONE | CUS_PG_DONE);
 | |
| 	nfi_write32(snf, SNF_STA_CTL1, 0);
 | |
| 
 | |
| 	/* Initialization on all dies */
 | |
| 	for (i = 0; i < snf->num_dies; i++) {
 | |
| 		mtk_snand_select_die(snf, i);
 | |
| 
 | |
| 		/* Disable On-Die ECC engine */
 | |
| 		ret = mtk_snand_ondie_ecc_control(snf, false);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 
 | |
| 		/* Disable block protection */
 | |
| 		mtk_snand_unlock(snf);
 | |
| 
 | |
| 		/* Enable/disable quad-spi */
 | |
| 		mtk_snand_qspi_control(snf, snf->quad_spi_op);
 | |
| 	}
 | |
| 
 | |
| 	mtk_snand_select_die(snf, 0);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mtk_snand_id_probe(struct mtk_snand *snf,
 | |
| 			      const struct snand_flash_info **snand_info)
 | |
| {
 | |
| 	uint8_t id[4], op[2];
 | |
| 	int ret;
 | |
| 
 | |
| 	/* Read SPI-NAND JEDEC ID, OP + dummy/addr + ID */
 | |
| 	op[0] = SNAND_CMD_READID;
 | |
| 	op[1] = 0;
 | |
| 	ret = mtk_snand_mac_io(snf, op, 2, id, sizeof(id));
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	*snand_info = snand_flash_id_lookup(SNAND_ID_DYMMY, id);
 | |
| 	if (*snand_info)
 | |
| 		return 0;
 | |
| 
 | |
| 	/* Read SPI-NAND JEDEC ID, OP + ID */
 | |
| 	op[0] = SNAND_CMD_READID;
 | |
| 	ret = mtk_snand_mac_io(snf, op, 1, id, sizeof(id));
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	*snand_info = snand_flash_id_lookup(SNAND_ID_DYMMY, id);
 | |
| 	if (*snand_info)
 | |
| 		return 0;
 | |
| 
 | |
| 	snand_log_chip(snf->pdev,
 | |
| 		       "Unrecognized SPI-NAND ID: %02x %02x %02x %02x\n",
 | |
| 		       id[0], id[1], id[2], id[3]);
 | |
| 
 | |
| 	return -EINVAL;
 | |
| }
 | |
| 
 | |
| int mtk_snand_init(void *dev, const struct mtk_snand_platdata *pdata,
 | |
| 		   struct mtk_snand **psnf)
 | |
| {
 | |
| 	const struct snand_flash_info *snand_info;
 | |
| 	uint32_t rawpage_size, sect_bf_size;
 | |
| 	struct mtk_snand tmpsnf, *snf;
 | |
| 	int ret;
 | |
| 
 | |
| 	if (!pdata || !psnf)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (pdata->soc >= __SNAND_SOC_MAX) {
 | |
| 		snand_log_chip(dev, "Invalid SOC %u for MTK-SNAND\n",
 | |
| 			       pdata->soc);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	/* Dummy instance only for initial reset and id probe */
 | |
| 	tmpsnf.nfi_base = pdata->nfi_base;
 | |
| 	tmpsnf.ecc_base = pdata->ecc_base;
 | |
| 	tmpsnf.soc = pdata->soc;
 | |
| 	tmpsnf.nfi_soc = &mtk_snand_socs[pdata->soc];
 | |
| 	tmpsnf.pdev = dev;
 | |
| 
 | |
| 	/* Switch to SNFI mode */
 | |
| 	writel(SPI_MODE, tmpsnf.nfi_base + SNF_CFG);
 | |
| 
 | |
| 	/* Reset SNFI & NFI */
 | |
| 	mtk_snand_mac_reset(&tmpsnf);
 | |
| 	mtk_nfi_reset(&tmpsnf);
 | |
| 
 | |
| 	/* Reset SPI-NAND chip */
 | |
| 	ret = mtk_snand_chip_reset(&tmpsnf);
 | |
| 	if (ret) {
 | |
| 		snand_log_chip(dev, "Failed to reset SPI-NAND chip\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	/* Probe SPI-NAND flash by JEDEC ID */
 | |
| 	ret = mtk_snand_id_probe(&tmpsnf, &snand_info);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	rawpage_size = snand_info->memorg.pagesize +
 | |
| 		       snand_info->memorg.sparesize;
 | |
| 
 | |
| 	sect_bf_size = mtk_snand_socs[pdata->soc].max_sectors *
 | |
| 		       sizeof(*snf->sect_bf);
 | |
| 
 | |
| 	/* Allocate memory for instance and cache */
 | |
| 	snf = generic_mem_alloc(dev,
 | |
| 				sizeof(*snf) + rawpage_size + sect_bf_size);
 | |
| 	if (!snf) {
 | |
| 		snand_log_chip(dev, "Failed to allocate memory for instance\n");
 | |
| 		return -ENOMEM;
 | |
| 	}
 | |
| 
 | |
| 	snf->sect_bf = (int *)((uintptr_t)snf + sizeof(*snf));
 | |
| 	snf->buf_cache = (uint8_t *)((uintptr_t)snf->sect_bf + sect_bf_size);
 | |
| 
 | |
| 	/* Allocate memory for DMA buffer */
 | |
| 	snf->page_cache = dma_mem_alloc(dev, rawpage_size);
 | |
| 	if (!snf->page_cache) {
 | |
| 		generic_mem_free(dev, snf);
 | |
| 		snand_log_chip(dev,
 | |
| 			       "Failed to allocate memory for DMA buffer\n");
 | |
| 		return -ENOMEM;
 | |
| 	}
 | |
| 
 | |
| 	/* Fill up instance */
 | |
| 	snf->pdev = dev;
 | |
| 	snf->nfi_base = pdata->nfi_base;
 | |
| 	snf->ecc_base = pdata->ecc_base;
 | |
| 	snf->soc = pdata->soc;
 | |
| 	snf->nfi_soc = &mtk_snand_socs[pdata->soc];
 | |
| 	snf->snfi_quad_spi = pdata->quad_spi;
 | |
| 
 | |
| 	/* Initialize SNFI & ECC engine */
 | |
| 	ret = mtk_snand_setup(snf, snand_info);
 | |
| 	if (ret) {
 | |
| 		dma_mem_free(dev, snf->page_cache);
 | |
| 		generic_mem_free(dev, snf);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	*psnf = snf;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int mtk_snand_cleanup(struct mtk_snand *snf)
 | |
| {
 | |
| 	if (!snf)
 | |
| 		return 0;
 | |
| 
 | |
| 	dma_mem_free(snf->pdev, snf->page_cache);
 | |
| 	generic_mem_free(snf->pdev, snf);
 | |
| 
 | |
| 	return 0;
 | |
| }
 |