xref: /linux/drivers/mtd/nand/raw/brcmnand/brcmnand.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
227c5b17cSBrian Norris /*
327c5b17cSBrian Norris  * Copyright © 2010-2015 Broadcom Corporation
427c5b17cSBrian Norris  */
527c5b17cSBrian Norris 
65c05bc00SSimon Arlott #include <linux/clk.h>
727c5b17cSBrian Norris #include <linux/module.h>
827c5b17cSBrian Norris #include <linux/init.h>
927c5b17cSBrian Norris #include <linux/delay.h>
1027c5b17cSBrian Norris #include <linux/device.h>
1127c5b17cSBrian Norris #include <linux/platform_device.h>
128e591300SFlorian Fainelli #include <linux/platform_data/brcmnand.h>
1327c5b17cSBrian Norris #include <linux/err.h>
1427c5b17cSBrian Norris #include <linux/completion.h>
1527c5b17cSBrian Norris #include <linux/interrupt.h>
1627c5b17cSBrian Norris #include <linux/spinlock.h>
1727c5b17cSBrian Norris #include <linux/dma-mapping.h>
1827c5b17cSBrian Norris #include <linux/ioport.h>
1927c5b17cSBrian Norris #include <linux/bug.h>
2027c5b17cSBrian Norris #include <linux/kernel.h>
2127c5b17cSBrian Norris #include <linux/bitops.h>
2227c5b17cSBrian Norris #include <linux/mm.h>
2327c5b17cSBrian Norris #include <linux/mtd/mtd.h>
24d4092d76SBoris Brezillon #include <linux/mtd/rawnand.h>
2527c5b17cSBrian Norris #include <linux/mtd/partitions.h>
2627c5b17cSBrian Norris #include <linux/of.h>
2727c5b17cSBrian Norris #include <linux/of_platform.h>
2827c5b17cSBrian Norris #include <linux/slab.h>
2925f97138SFlorian Fainelli #include <linux/static_key.h>
3027c5b17cSBrian Norris #include <linux/list.h>
3127c5b17cSBrian Norris #include <linux/log2.h>
3227c5b17cSBrian Norris 
3327c5b17cSBrian Norris #include "brcmnand.h"
3427c5b17cSBrian Norris 
3527c5b17cSBrian Norris /*
3627c5b17cSBrian Norris  * This flag controls if WP stays on between erase/write commands to mitigate
3727c5b17cSBrian Norris  * flash corruption due to power glitches. Values:
3827c5b17cSBrian Norris  * 0: NAND_WP is not used or not available
3927c5b17cSBrian Norris  * 1: NAND_WP is set by default, cleared for erase/write operations
4027c5b17cSBrian Norris  * 2: NAND_WP is always cleared
4127c5b17cSBrian Norris  */
4227c5b17cSBrian Norris static int wp_on = 1;
4327c5b17cSBrian Norris module_param(wp_on, int, 0444);
4427c5b17cSBrian Norris 
4527c5b17cSBrian Norris /***********************************************************************
4627c5b17cSBrian Norris  * Definitions
4727c5b17cSBrian Norris  ***********************************************************************/
4827c5b17cSBrian Norris 
4927c5b17cSBrian Norris #define DRV_NAME			"brcmnand"
5027c5b17cSBrian Norris 
5127c5b17cSBrian Norris #define CMD_NULL			0x00
5227c5b17cSBrian Norris #define CMD_PAGE_READ			0x01
5327c5b17cSBrian Norris #define CMD_SPARE_AREA_READ		0x02
5427c5b17cSBrian Norris #define CMD_STATUS_READ			0x03
5527c5b17cSBrian Norris #define CMD_PROGRAM_PAGE		0x04
5627c5b17cSBrian Norris #define CMD_PROGRAM_SPARE_AREA		0x05
5727c5b17cSBrian Norris #define CMD_COPY_BACK			0x06
5827c5b17cSBrian Norris #define CMD_DEVICE_ID_READ		0x07
5927c5b17cSBrian Norris #define CMD_BLOCK_ERASE			0x08
6027c5b17cSBrian Norris #define CMD_FLASH_RESET			0x09
6127c5b17cSBrian Norris #define CMD_BLOCKS_LOCK			0x0a
6227c5b17cSBrian Norris #define CMD_BLOCKS_LOCK_DOWN		0x0b
6327c5b17cSBrian Norris #define CMD_BLOCKS_UNLOCK		0x0c
6427c5b17cSBrian Norris #define CMD_READ_BLOCKS_LOCK_STATUS	0x0d
6527c5b17cSBrian Norris #define CMD_PARAMETER_READ		0x0e
6627c5b17cSBrian Norris #define CMD_PARAMETER_CHANGE_COL	0x0f
6727c5b17cSBrian Norris #define CMD_LOW_LEVEL_OP		0x10
6827c5b17cSBrian Norris 
6927c5b17cSBrian Norris struct brcm_nand_dma_desc {
7027c5b17cSBrian Norris 	u32 next_desc;
7127c5b17cSBrian Norris 	u32 next_desc_ext;
7227c5b17cSBrian Norris 	u32 cmd_irq;
7327c5b17cSBrian Norris 	u32 dram_addr;
7427c5b17cSBrian Norris 	u32 dram_addr_ext;
7527c5b17cSBrian Norris 	u32 tfr_len;
7627c5b17cSBrian Norris 	u32 total_len;
7727c5b17cSBrian Norris 	u32 flash_addr;
7827c5b17cSBrian Norris 	u32 flash_addr_ext;
7927c5b17cSBrian Norris 	u32 cs;
8027c5b17cSBrian Norris 	u32 pad2[5];
8127c5b17cSBrian Norris 	u32 status_valid;
8227c5b17cSBrian Norris } __packed;
8327c5b17cSBrian Norris 
8427c5b17cSBrian Norris /* Bitfields for brcm_nand_dma_desc::status_valid */
8527c5b17cSBrian Norris #define FLASH_DMA_ECC_ERROR	(1 << 8)
8627c5b17cSBrian Norris #define FLASH_DMA_CORR_ERROR	(1 << 9)
8727c5b17cSBrian Norris 
880c06da57SKamal Dasu /* Bitfields for DMA_MODE */
890c06da57SKamal Dasu #define FLASH_DMA_MODE_STOP_ON_ERROR	BIT(1) /* stop in Uncorr ECC error */
900c06da57SKamal Dasu #define FLASH_DMA_MODE_MODE		BIT(0) /* link list */
910c06da57SKamal Dasu #define FLASH_DMA_MODE_MASK		(FLASH_DMA_MODE_STOP_ON_ERROR |	\
920c06da57SKamal Dasu 						FLASH_DMA_MODE_MODE)
930c06da57SKamal Dasu 
9427c5b17cSBrian Norris /* 512B flash cache in the NAND controller HW */
9527c5b17cSBrian Norris #define FC_SHIFT		9U
9627c5b17cSBrian Norris #define FC_BYTES		512U
9727c5b17cSBrian Norris #define FC_WORDS		(FC_BYTES >> 2)
9827c5b17cSBrian Norris 
9927c5b17cSBrian Norris #define BRCMNAND_MIN_PAGESIZE	512
10027c5b17cSBrian Norris #define BRCMNAND_MIN_BLOCKSIZE	(8 * 1024)
10127c5b17cSBrian Norris #define BRCMNAND_MIN_DEVSIZE	(4ULL * 1024 * 1024)
10227c5b17cSBrian Norris 
1039d2ee0a6SKamal Dasu #define NAND_CTRL_RDY			(INTFC_CTLR_READY | INTFC_FLASH_READY)
1049d2ee0a6SKamal Dasu #define NAND_POLL_STATUS_TIMEOUT_MS	100
1059d2ee0a6SKamal Dasu 
106a5d53ad2SKamal Dasu #define EDU_CMD_WRITE          0x00
107a5d53ad2SKamal Dasu #define EDU_CMD_READ           0x01
108a5d53ad2SKamal Dasu #define EDU_STATUS_ACTIVE      BIT(0)
109a5d53ad2SKamal Dasu #define EDU_ERR_STATUS_ERRACK  BIT(0)
110a5d53ad2SKamal Dasu #define EDU_DONE_MASK		GENMASK(1, 0)
111a5d53ad2SKamal Dasu 
112a5d53ad2SKamal Dasu #define EDU_CONFIG_MODE_NAND   BIT(0)
113a5d53ad2SKamal Dasu #define EDU_CONFIG_SWAP_BYTE   BIT(1)
114a5d53ad2SKamal Dasu #ifdef CONFIG_CPU_BIG_ENDIAN
115a5d53ad2SKamal Dasu #define EDU_CONFIG_SWAP_CFG     EDU_CONFIG_SWAP_BYTE
116a5d53ad2SKamal Dasu #else
117a5d53ad2SKamal Dasu #define EDU_CONFIG_SWAP_CFG     0
118a5d53ad2SKamal Dasu #endif
119a5d53ad2SKamal Dasu 
120a5d53ad2SKamal Dasu /* edu registers */
121a5d53ad2SKamal Dasu enum edu_reg {
122a5d53ad2SKamal Dasu 	EDU_CONFIG = 0,
123a5d53ad2SKamal Dasu 	EDU_DRAM_ADDR,
124a5d53ad2SKamal Dasu 	EDU_EXT_ADDR,
125a5d53ad2SKamal Dasu 	EDU_LENGTH,
126a5d53ad2SKamal Dasu 	EDU_CMD,
127a5d53ad2SKamal Dasu 	EDU_STOP,
128a5d53ad2SKamal Dasu 	EDU_STATUS,
129a5d53ad2SKamal Dasu 	EDU_DONE,
130a5d53ad2SKamal Dasu 	EDU_ERR_STATUS,
131a5d53ad2SKamal Dasu };
132a5d53ad2SKamal Dasu 
133a5d53ad2SKamal Dasu static const u16  edu_regs[] = {
134a5d53ad2SKamal Dasu 	[EDU_CONFIG] = 0x00,
135a5d53ad2SKamal Dasu 	[EDU_DRAM_ADDR] = 0x04,
136a5d53ad2SKamal Dasu 	[EDU_EXT_ADDR] = 0x08,
137a5d53ad2SKamal Dasu 	[EDU_LENGTH] = 0x0c,
138a5d53ad2SKamal Dasu 	[EDU_CMD] = 0x10,
139a5d53ad2SKamal Dasu 	[EDU_STOP] = 0x14,
140a5d53ad2SKamal Dasu 	[EDU_STATUS] = 0x18,
141a5d53ad2SKamal Dasu 	[EDU_DONE] = 0x1c,
142a5d53ad2SKamal Dasu 	[EDU_ERR_STATUS] = 0x20,
143a5d53ad2SKamal Dasu };
144a5d53ad2SKamal Dasu 
1450c06da57SKamal Dasu /* flash_dma registers */
1460c06da57SKamal Dasu enum flash_dma_reg {
1470c06da57SKamal Dasu 	FLASH_DMA_REVISION = 0,
1480c06da57SKamal Dasu 	FLASH_DMA_FIRST_DESC,
1490c06da57SKamal Dasu 	FLASH_DMA_FIRST_DESC_EXT,
1500c06da57SKamal Dasu 	FLASH_DMA_CTRL,
1510c06da57SKamal Dasu 	FLASH_DMA_MODE,
1520c06da57SKamal Dasu 	FLASH_DMA_STATUS,
1530c06da57SKamal Dasu 	FLASH_DMA_INTERRUPT_DESC,
1540c06da57SKamal Dasu 	FLASH_DMA_INTERRUPT_DESC_EXT,
1550c06da57SKamal Dasu 	FLASH_DMA_ERROR_STATUS,
1560c06da57SKamal Dasu 	FLASH_DMA_CURRENT_DESC,
1570c06da57SKamal Dasu 	FLASH_DMA_CURRENT_DESC_EXT,
1580c06da57SKamal Dasu };
1590c06da57SKamal Dasu 
16083156c1cSKamal Dasu /* flash_dma registers v0*/
16183156c1cSKamal Dasu static const u16 flash_dma_regs_v0[] = {
16283156c1cSKamal Dasu 	[FLASH_DMA_REVISION]		= 0x00,
16383156c1cSKamal Dasu 	[FLASH_DMA_FIRST_DESC]		= 0x04,
16483156c1cSKamal Dasu 	[FLASH_DMA_CTRL]		= 0x08,
16583156c1cSKamal Dasu 	[FLASH_DMA_MODE]		= 0x0c,
16683156c1cSKamal Dasu 	[FLASH_DMA_STATUS]		= 0x10,
16783156c1cSKamal Dasu 	[FLASH_DMA_INTERRUPT_DESC]	= 0x14,
16883156c1cSKamal Dasu 	[FLASH_DMA_ERROR_STATUS]	= 0x18,
16983156c1cSKamal Dasu 	[FLASH_DMA_CURRENT_DESC]	= 0x1c,
17083156c1cSKamal Dasu };
17183156c1cSKamal Dasu 
1720c06da57SKamal Dasu /* flash_dma registers v1*/
1730c06da57SKamal Dasu static const u16 flash_dma_regs_v1[] = {
1740c06da57SKamal Dasu 	[FLASH_DMA_REVISION]		= 0x00,
1750c06da57SKamal Dasu 	[FLASH_DMA_FIRST_DESC]		= 0x04,
1760c06da57SKamal Dasu 	[FLASH_DMA_FIRST_DESC_EXT]	= 0x08,
1770c06da57SKamal Dasu 	[FLASH_DMA_CTRL]		= 0x0c,
1780c06da57SKamal Dasu 	[FLASH_DMA_MODE]		= 0x10,
1790c06da57SKamal Dasu 	[FLASH_DMA_STATUS]		= 0x14,
1800c06da57SKamal Dasu 	[FLASH_DMA_INTERRUPT_DESC]	= 0x18,
1810c06da57SKamal Dasu 	[FLASH_DMA_INTERRUPT_DESC_EXT]	= 0x1c,
1820c06da57SKamal Dasu 	[FLASH_DMA_ERROR_STATUS]	= 0x20,
1830c06da57SKamal Dasu 	[FLASH_DMA_CURRENT_DESC]	= 0x24,
1840c06da57SKamal Dasu 	[FLASH_DMA_CURRENT_DESC_EXT]	= 0x28,
1850c06da57SKamal Dasu };
1860c06da57SKamal Dasu 
1870c06da57SKamal Dasu /* flash_dma registers v4 */
1880c06da57SKamal Dasu static const u16 flash_dma_regs_v4[] = {
1890c06da57SKamal Dasu 	[FLASH_DMA_REVISION]		= 0x00,
1900c06da57SKamal Dasu 	[FLASH_DMA_FIRST_DESC]		= 0x08,
1910c06da57SKamal Dasu 	[FLASH_DMA_FIRST_DESC_EXT]	= 0x0c,
1920c06da57SKamal Dasu 	[FLASH_DMA_CTRL]		= 0x10,
1930c06da57SKamal Dasu 	[FLASH_DMA_MODE]		= 0x14,
1940c06da57SKamal Dasu 	[FLASH_DMA_STATUS]		= 0x18,
1950c06da57SKamal Dasu 	[FLASH_DMA_INTERRUPT_DESC]	= 0x20,
1960c06da57SKamal Dasu 	[FLASH_DMA_INTERRUPT_DESC_EXT]	= 0x24,
1970c06da57SKamal Dasu 	[FLASH_DMA_ERROR_STATUS]	= 0x28,
1980c06da57SKamal Dasu 	[FLASH_DMA_CURRENT_DESC]	= 0x30,
1990c06da57SKamal Dasu 	[FLASH_DMA_CURRENT_DESC_EXT]	= 0x34,
2000c06da57SKamal Dasu };
2010c06da57SKamal Dasu 
20227c5b17cSBrian Norris /* Controller feature flags */
20327c5b17cSBrian Norris enum {
20427c5b17cSBrian Norris 	BRCMNAND_HAS_1K_SECTORS			= BIT(0),
20527c5b17cSBrian Norris 	BRCMNAND_HAS_PREFETCH			= BIT(1),
20627c5b17cSBrian Norris 	BRCMNAND_HAS_CACHE_MODE			= BIT(2),
20727c5b17cSBrian Norris 	BRCMNAND_HAS_WP				= BIT(3),
20827c5b17cSBrian Norris };
20927c5b17cSBrian Norris 
210a5d53ad2SKamal Dasu struct brcmnand_host;
211a5d53ad2SKamal Dasu 
21225f97138SFlorian Fainelli static DEFINE_STATIC_KEY_FALSE(brcmnand_soc_has_ops_key);
21325f97138SFlorian Fainelli 
21427c5b17cSBrian Norris struct brcmnand_controller {
21527c5b17cSBrian Norris 	struct device		*dev;
2167da45139SMiquel Raynal 	struct nand_controller	controller;
21727c5b17cSBrian Norris 	void __iomem		*nand_base;
21827c5b17cSBrian Norris 	void __iomem		*nand_fc; /* flash cache */
21927c5b17cSBrian Norris 	void __iomem		*flash_dma_base;
220f5619f37SFlorian Fainelli 	int			irq;
22127c5b17cSBrian Norris 	unsigned int		dma_irq;
22227c5b17cSBrian Norris 	int			nand_version;
22327c5b17cSBrian Norris 
224c26211d3SBrian Norris 	/* Some SoCs provide custom interrupt status register(s) */
225c26211d3SBrian Norris 	struct brcmnand_soc	*soc;
226c26211d3SBrian Norris 
2275c05bc00SSimon Arlott 	/* Some SoCs have a gateable clock for the controller */
2285c05bc00SSimon Arlott 	struct clk		*clk;
2295c05bc00SSimon Arlott 
23027c5b17cSBrian Norris 	int			cmd_pending;
23127c5b17cSBrian Norris 	bool			dma_pending;
232a5d53ad2SKamal Dasu 	bool                    edu_pending;
23327c5b17cSBrian Norris 	struct completion	done;
23427c5b17cSBrian Norris 	struct completion	dma_done;
235a5d53ad2SKamal Dasu 	struct completion       edu_done;
23627c5b17cSBrian Norris 
23727c5b17cSBrian Norris 	/* List of NAND hosts (one for each chip-select) */
23827c5b17cSBrian Norris 	struct list_head host_list;
23927c5b17cSBrian Norris 
240a5d53ad2SKamal Dasu 	/* EDU info, per-transaction */
241a5d53ad2SKamal Dasu 	const u16               *edu_offsets;
242a5d53ad2SKamal Dasu 	void __iomem            *edu_base;
243a5d53ad2SKamal Dasu 	int			edu_irq;
244a5d53ad2SKamal Dasu 	int                     edu_count;
245a5d53ad2SKamal Dasu 	u64                     edu_dram_addr;
246a5d53ad2SKamal Dasu 	u32                     edu_ext_addr;
247a5d53ad2SKamal Dasu 	u32                     edu_cmd;
248a5d53ad2SKamal Dasu 	u32                     edu_config;
249a0719126SKamal Dasu 	int			sas; /* spare area size, per flash cache */
250a0719126SKamal Dasu 	int			sector_size_1k;
251a0719126SKamal Dasu 	u8			*oob;
252a5d53ad2SKamal Dasu 
2530c06da57SKamal Dasu 	/* flash_dma reg */
2540c06da57SKamal Dasu 	const u16		*flash_dma_offsets;
25527c5b17cSBrian Norris 	struct brcm_nand_dma_desc *dma_desc;
25627c5b17cSBrian Norris 	dma_addr_t		dma_pa;
25727c5b17cSBrian Norris 
258a5d53ad2SKamal Dasu 	int (*dma_trans)(struct brcmnand_host *host, u64 addr, u32 *buf,
259a0719126SKamal Dasu 			 u8 *oob, u32 len, u8 dma_cmd);
260a5d53ad2SKamal Dasu 
26127c5b17cSBrian Norris 	/* in-memory cache of the FLASH_CACHE, used only for some commands */
262d618baf9SBrian Norris 	u8			flash_cache[FC_BYTES];
26327c5b17cSBrian Norris 
26427c5b17cSBrian Norris 	/* Controller revision details */
26527c5b17cSBrian Norris 	const u16		*reg_offsets;
26627c5b17cSBrian Norris 	unsigned int		reg_spacing; /* between CS1, CS2, ... regs */
26727c5b17cSBrian Norris 	const u8		*cs_offsets; /* within each chip-select */
26827c5b17cSBrian Norris 	const u8		*cs0_offsets; /* within CS0, if different */
26927c5b17cSBrian Norris 	unsigned int		max_block_size;
27027c5b17cSBrian Norris 	const unsigned int	*block_sizes;
27127c5b17cSBrian Norris 	unsigned int		max_page_size;
27227c5b17cSBrian Norris 	const unsigned int	*page_sizes;
2737e7c7df5SÁlvaro Fernández Rojas 	unsigned int		page_size_shift;
27427c5b17cSBrian Norris 	unsigned int		max_oob;
27527c5b17cSBrian Norris 	u32			features;
27627c5b17cSBrian Norris 
27727c5b17cSBrian Norris 	/* for low-power standby/resume only */
27827c5b17cSBrian Norris 	u32			nand_cs_nand_select;
27927c5b17cSBrian Norris 	u32			nand_cs_nand_xor;
28027c5b17cSBrian Norris 	u32			corr_stat_threshold;
28127c5b17cSBrian Norris 	u32			flash_dma_mode;
282a5d53ad2SKamal Dasu 	u32                     flash_edu_mode;
283c1ac2dc3SKamal Dasu 	bool			pio_poll_mode;
28427c5b17cSBrian Norris };
28527c5b17cSBrian Norris 
28627c5b17cSBrian Norris struct brcmnand_cfg {
28727c5b17cSBrian Norris 	u64			device_size;
28827c5b17cSBrian Norris 	unsigned int		block_size;
28927c5b17cSBrian Norris 	unsigned int		page_size;
29027c5b17cSBrian Norris 	unsigned int		spare_area_size;
29127c5b17cSBrian Norris 	unsigned int		device_width;
29227c5b17cSBrian Norris 	unsigned int		col_adr_bytes;
29327c5b17cSBrian Norris 	unsigned int		blk_adr_bytes;
29427c5b17cSBrian Norris 	unsigned int		ful_adr_bytes;
29527c5b17cSBrian Norris 	unsigned int		sector_size_1k;
29627c5b17cSBrian Norris 	unsigned int		ecc_level;
29727c5b17cSBrian Norris 	/* use for low-power standby/resume only */
29827c5b17cSBrian Norris 	u32			acc_control;
29927c5b17cSBrian Norris 	u32			config;
30027c5b17cSBrian Norris 	u32			config_ext;
30127c5b17cSBrian Norris 	u32			timing_1;
30227c5b17cSBrian Norris 	u32			timing_2;
30327c5b17cSBrian Norris };
30427c5b17cSBrian Norris 
30527c5b17cSBrian Norris struct brcmnand_host {
30627c5b17cSBrian Norris 	struct list_head	node;
30727c5b17cSBrian Norris 
30827c5b17cSBrian Norris 	struct nand_chip	chip;
30927c5b17cSBrian Norris 	struct platform_device	*pdev;
31027c5b17cSBrian Norris 	int			cs;
31127c5b17cSBrian Norris 
31227c5b17cSBrian Norris 	unsigned int		last_cmd;
31327c5b17cSBrian Norris 	unsigned int		last_byte;
31427c5b17cSBrian Norris 	u64			last_addr;
31527c5b17cSBrian Norris 	struct brcmnand_cfg	hwcfg;
31627c5b17cSBrian Norris 	struct brcmnand_controller *ctrl;
31727c5b17cSBrian Norris };
31827c5b17cSBrian Norris 
31927c5b17cSBrian Norris enum brcmnand_reg {
32027c5b17cSBrian Norris 	BRCMNAND_CMD_START = 0,
32127c5b17cSBrian Norris 	BRCMNAND_CMD_EXT_ADDRESS,
32227c5b17cSBrian Norris 	BRCMNAND_CMD_ADDRESS,
32327c5b17cSBrian Norris 	BRCMNAND_INTFC_STATUS,
32427c5b17cSBrian Norris 	BRCMNAND_CS_SELECT,
32527c5b17cSBrian Norris 	BRCMNAND_CS_XOR,
32627c5b17cSBrian Norris 	BRCMNAND_LL_OP,
32727c5b17cSBrian Norris 	BRCMNAND_CS0_BASE,
32827c5b17cSBrian Norris 	BRCMNAND_CS1_BASE,		/* CS1 regs, if non-contiguous */
32927c5b17cSBrian Norris 	BRCMNAND_CORR_THRESHOLD,
33027c5b17cSBrian Norris 	BRCMNAND_CORR_THRESHOLD_EXT,
33127c5b17cSBrian Norris 	BRCMNAND_UNCORR_COUNT,
33227c5b17cSBrian Norris 	BRCMNAND_CORR_COUNT,
33327c5b17cSBrian Norris 	BRCMNAND_CORR_EXT_ADDR,
33427c5b17cSBrian Norris 	BRCMNAND_CORR_ADDR,
33527c5b17cSBrian Norris 	BRCMNAND_UNCORR_EXT_ADDR,
33627c5b17cSBrian Norris 	BRCMNAND_UNCORR_ADDR,
33727c5b17cSBrian Norris 	BRCMNAND_SEMAPHORE,
33827c5b17cSBrian Norris 	BRCMNAND_ID,
33927c5b17cSBrian Norris 	BRCMNAND_ID_EXT,
34027c5b17cSBrian Norris 	BRCMNAND_LL_RDATA,
34127c5b17cSBrian Norris 	BRCMNAND_OOB_READ_BASE,
34227c5b17cSBrian Norris 	BRCMNAND_OOB_READ_10_BASE,	/* offset 0x10, if non-contiguous */
34327c5b17cSBrian Norris 	BRCMNAND_OOB_WRITE_BASE,
34427c5b17cSBrian Norris 	BRCMNAND_OOB_WRITE_10_BASE,	/* offset 0x10, if non-contiguous */
34527c5b17cSBrian Norris 	BRCMNAND_FC_BASE,
34627c5b17cSBrian Norris };
34727c5b17cSBrian Norris 
3487e7c7df5SÁlvaro Fernández Rojas /* BRCMNAND v2.1-v2.2 */
3497e7c7df5SÁlvaro Fernández Rojas static const u16 brcmnand_regs_v21[] = {
3507e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_CMD_START]		=  0x04,
3517e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_CMD_EXT_ADDRESS]	=  0x08,
3527e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_CMD_ADDRESS]		=  0x0c,
3537e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_INTFC_STATUS]		=  0x5c,
3547e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_CS_SELECT]		=  0x14,
3557e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_CS_XOR]		=  0x18,
3567e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_LL_OP]		=     0,
3577e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_CS0_BASE]		=  0x40,
3587e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_CS1_BASE]		=     0,
3597e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_CORR_THRESHOLD]	=     0,
3607e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_CORR_THRESHOLD_EXT]	=     0,
3617e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_UNCORR_COUNT]		=     0,
3627e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_CORR_COUNT]		=     0,
3637e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_CORR_EXT_ADDR]	=  0x60,
3647e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_CORR_ADDR]		=  0x64,
3657e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_UNCORR_EXT_ADDR]	=  0x68,
3667e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_UNCORR_ADDR]		=  0x6c,
3677e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_SEMAPHORE]		=  0x50,
3687e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_ID]			=  0x54,
3697e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_ID_EXT]		=     0,
3707e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_LL_RDATA]		=     0,
3717e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_OOB_READ_BASE]	=  0x20,
3727e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_OOB_READ_10_BASE]	=     0,
3737e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_OOB_WRITE_BASE]	=  0x30,
3747e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_OOB_WRITE_10_BASE]	=     0,
3757e7c7df5SÁlvaro Fernández Rojas 	[BRCMNAND_FC_BASE]		= 0x200,
3767e7c7df5SÁlvaro Fernández Rojas };
3777e7c7df5SÁlvaro Fernández Rojas 
3784fd63909SÁlvaro Fernández Rojas /* BRCMNAND v3.3-v4.0 */
3794fd63909SÁlvaro Fernández Rojas static const u16 brcmnand_regs_v33[] = {
38027c5b17cSBrian Norris 	[BRCMNAND_CMD_START]		=  0x04,
38127c5b17cSBrian Norris 	[BRCMNAND_CMD_EXT_ADDRESS]	=  0x08,
38227c5b17cSBrian Norris 	[BRCMNAND_CMD_ADDRESS]		=  0x0c,
38327c5b17cSBrian Norris 	[BRCMNAND_INTFC_STATUS]		=  0x6c,
38427c5b17cSBrian Norris 	[BRCMNAND_CS_SELECT]		=  0x14,
38527c5b17cSBrian Norris 	[BRCMNAND_CS_XOR]		=  0x18,
38627c5b17cSBrian Norris 	[BRCMNAND_LL_OP]		= 0x178,
38727c5b17cSBrian Norris 	[BRCMNAND_CS0_BASE]		=  0x40,
38827c5b17cSBrian Norris 	[BRCMNAND_CS1_BASE]		=  0xd0,
38927c5b17cSBrian Norris 	[BRCMNAND_CORR_THRESHOLD]	=  0x84,
39027c5b17cSBrian Norris 	[BRCMNAND_CORR_THRESHOLD_EXT]	=     0,
39127c5b17cSBrian Norris 	[BRCMNAND_UNCORR_COUNT]		=     0,
39227c5b17cSBrian Norris 	[BRCMNAND_CORR_COUNT]		=     0,
39327c5b17cSBrian Norris 	[BRCMNAND_CORR_EXT_ADDR]	=  0x70,
39427c5b17cSBrian Norris 	[BRCMNAND_CORR_ADDR]		=  0x74,
39527c5b17cSBrian Norris 	[BRCMNAND_UNCORR_EXT_ADDR]	=  0x78,
39627c5b17cSBrian Norris 	[BRCMNAND_UNCORR_ADDR]		=  0x7c,
39727c5b17cSBrian Norris 	[BRCMNAND_SEMAPHORE]		=  0x58,
39827c5b17cSBrian Norris 	[BRCMNAND_ID]			=  0x60,
39927c5b17cSBrian Norris 	[BRCMNAND_ID_EXT]		=  0x64,
40027c5b17cSBrian Norris 	[BRCMNAND_LL_RDATA]		= 0x17c,
40127c5b17cSBrian Norris 	[BRCMNAND_OOB_READ_BASE]	=  0x20,
40227c5b17cSBrian Norris 	[BRCMNAND_OOB_READ_10_BASE]	= 0x130,
40327c5b17cSBrian Norris 	[BRCMNAND_OOB_WRITE_BASE]	=  0x30,
40427c5b17cSBrian Norris 	[BRCMNAND_OOB_WRITE_10_BASE]	=     0,
40527c5b17cSBrian Norris 	[BRCMNAND_FC_BASE]		= 0x200,
40627c5b17cSBrian Norris };
40727c5b17cSBrian Norris 
40827c5b17cSBrian Norris /* BRCMNAND v5.0 */
40927c5b17cSBrian Norris static const u16 brcmnand_regs_v50[] = {
41027c5b17cSBrian Norris 	[BRCMNAND_CMD_START]		=  0x04,
41127c5b17cSBrian Norris 	[BRCMNAND_CMD_EXT_ADDRESS]	=  0x08,
41227c5b17cSBrian Norris 	[BRCMNAND_CMD_ADDRESS]		=  0x0c,
41327c5b17cSBrian Norris 	[BRCMNAND_INTFC_STATUS]		=  0x6c,
41427c5b17cSBrian Norris 	[BRCMNAND_CS_SELECT]		=  0x14,
41527c5b17cSBrian Norris 	[BRCMNAND_CS_XOR]		=  0x18,
41627c5b17cSBrian Norris 	[BRCMNAND_LL_OP]		= 0x178,
41727c5b17cSBrian Norris 	[BRCMNAND_CS0_BASE]		=  0x40,
41827c5b17cSBrian Norris 	[BRCMNAND_CS1_BASE]		=  0xd0,
41927c5b17cSBrian Norris 	[BRCMNAND_CORR_THRESHOLD]	=  0x84,
42027c5b17cSBrian Norris 	[BRCMNAND_CORR_THRESHOLD_EXT]	=     0,
42127c5b17cSBrian Norris 	[BRCMNAND_UNCORR_COUNT]		=     0,
42227c5b17cSBrian Norris 	[BRCMNAND_CORR_COUNT]		=     0,
42327c5b17cSBrian Norris 	[BRCMNAND_CORR_EXT_ADDR]	=  0x70,
42427c5b17cSBrian Norris 	[BRCMNAND_CORR_ADDR]		=  0x74,
42527c5b17cSBrian Norris 	[BRCMNAND_UNCORR_EXT_ADDR]	=  0x78,
42627c5b17cSBrian Norris 	[BRCMNAND_UNCORR_ADDR]		=  0x7c,
42727c5b17cSBrian Norris 	[BRCMNAND_SEMAPHORE]		=  0x58,
42827c5b17cSBrian Norris 	[BRCMNAND_ID]			=  0x60,
42927c5b17cSBrian Norris 	[BRCMNAND_ID_EXT]		=  0x64,
43027c5b17cSBrian Norris 	[BRCMNAND_LL_RDATA]		= 0x17c,
43127c5b17cSBrian Norris 	[BRCMNAND_OOB_READ_BASE]	=  0x20,
43227c5b17cSBrian Norris 	[BRCMNAND_OOB_READ_10_BASE]	= 0x130,
43327c5b17cSBrian Norris 	[BRCMNAND_OOB_WRITE_BASE]	=  0x30,
43427c5b17cSBrian Norris 	[BRCMNAND_OOB_WRITE_10_BASE]	= 0x140,
43527c5b17cSBrian Norris 	[BRCMNAND_FC_BASE]		= 0x200,
43627c5b17cSBrian Norris };
43727c5b17cSBrian Norris 
43827c5b17cSBrian Norris /* BRCMNAND v6.0 - v7.1 */
43927c5b17cSBrian Norris static const u16 brcmnand_regs_v60[] = {
44027c5b17cSBrian Norris 	[BRCMNAND_CMD_START]		=  0x04,
44127c5b17cSBrian Norris 	[BRCMNAND_CMD_EXT_ADDRESS]	=  0x08,
44227c5b17cSBrian Norris 	[BRCMNAND_CMD_ADDRESS]		=  0x0c,
44327c5b17cSBrian Norris 	[BRCMNAND_INTFC_STATUS]		=  0x14,
44427c5b17cSBrian Norris 	[BRCMNAND_CS_SELECT]		=  0x18,
44527c5b17cSBrian Norris 	[BRCMNAND_CS_XOR]		=  0x1c,
44627c5b17cSBrian Norris 	[BRCMNAND_LL_OP]		=  0x20,
44727c5b17cSBrian Norris 	[BRCMNAND_CS0_BASE]		=  0x50,
44827c5b17cSBrian Norris 	[BRCMNAND_CS1_BASE]		=     0,
44927c5b17cSBrian Norris 	[BRCMNAND_CORR_THRESHOLD]	=  0xc0,
45027c5b17cSBrian Norris 	[BRCMNAND_CORR_THRESHOLD_EXT]	=  0xc4,
45127c5b17cSBrian Norris 	[BRCMNAND_UNCORR_COUNT]		=  0xfc,
45227c5b17cSBrian Norris 	[BRCMNAND_CORR_COUNT]		= 0x100,
45327c5b17cSBrian Norris 	[BRCMNAND_CORR_EXT_ADDR]	= 0x10c,
45427c5b17cSBrian Norris 	[BRCMNAND_CORR_ADDR]		= 0x110,
45527c5b17cSBrian Norris 	[BRCMNAND_UNCORR_EXT_ADDR]	= 0x114,
45627c5b17cSBrian Norris 	[BRCMNAND_UNCORR_ADDR]		= 0x118,
45727c5b17cSBrian Norris 	[BRCMNAND_SEMAPHORE]		= 0x150,
45827c5b17cSBrian Norris 	[BRCMNAND_ID]			= 0x194,
45927c5b17cSBrian Norris 	[BRCMNAND_ID_EXT]		= 0x198,
46027c5b17cSBrian Norris 	[BRCMNAND_LL_RDATA]		= 0x19c,
46127c5b17cSBrian Norris 	[BRCMNAND_OOB_READ_BASE]	= 0x200,
46227c5b17cSBrian Norris 	[BRCMNAND_OOB_READ_10_BASE]	=     0,
46327c5b17cSBrian Norris 	[BRCMNAND_OOB_WRITE_BASE]	= 0x280,
46427c5b17cSBrian Norris 	[BRCMNAND_OOB_WRITE_10_BASE]	=     0,
46527c5b17cSBrian Norris 	[BRCMNAND_FC_BASE]		= 0x400,
46627c5b17cSBrian Norris };
46727c5b17cSBrian Norris 
468d267aefcSFlorian Fainelli /* BRCMNAND v7.1 */
469d267aefcSFlorian Fainelli static const u16 brcmnand_regs_v71[] = {
470d267aefcSFlorian Fainelli 	[BRCMNAND_CMD_START]		=  0x04,
471d267aefcSFlorian Fainelli 	[BRCMNAND_CMD_EXT_ADDRESS]	=  0x08,
472d267aefcSFlorian Fainelli 	[BRCMNAND_CMD_ADDRESS]		=  0x0c,
473d267aefcSFlorian Fainelli 	[BRCMNAND_INTFC_STATUS]		=  0x14,
474d267aefcSFlorian Fainelli 	[BRCMNAND_CS_SELECT]		=  0x18,
475d267aefcSFlorian Fainelli 	[BRCMNAND_CS_XOR]		=  0x1c,
476d267aefcSFlorian Fainelli 	[BRCMNAND_LL_OP]		=  0x20,
477d267aefcSFlorian Fainelli 	[BRCMNAND_CS0_BASE]		=  0x50,
478d267aefcSFlorian Fainelli 	[BRCMNAND_CS1_BASE]		=     0,
479d267aefcSFlorian Fainelli 	[BRCMNAND_CORR_THRESHOLD]	=  0xdc,
480d267aefcSFlorian Fainelli 	[BRCMNAND_CORR_THRESHOLD_EXT]	=  0xe0,
481d267aefcSFlorian Fainelli 	[BRCMNAND_UNCORR_COUNT]		=  0xfc,
482d267aefcSFlorian Fainelli 	[BRCMNAND_CORR_COUNT]		= 0x100,
483d267aefcSFlorian Fainelli 	[BRCMNAND_CORR_EXT_ADDR]	= 0x10c,
484d267aefcSFlorian Fainelli 	[BRCMNAND_CORR_ADDR]		= 0x110,
485d267aefcSFlorian Fainelli 	[BRCMNAND_UNCORR_EXT_ADDR]	= 0x114,
486d267aefcSFlorian Fainelli 	[BRCMNAND_UNCORR_ADDR]		= 0x118,
487d267aefcSFlorian Fainelli 	[BRCMNAND_SEMAPHORE]		= 0x150,
488d267aefcSFlorian Fainelli 	[BRCMNAND_ID]			= 0x194,
489d267aefcSFlorian Fainelli 	[BRCMNAND_ID_EXT]		= 0x198,
490d267aefcSFlorian Fainelli 	[BRCMNAND_LL_RDATA]		= 0x19c,
491d267aefcSFlorian Fainelli 	[BRCMNAND_OOB_READ_BASE]	= 0x200,
492d267aefcSFlorian Fainelli 	[BRCMNAND_OOB_READ_10_BASE]	=     0,
493d267aefcSFlorian Fainelli 	[BRCMNAND_OOB_WRITE_BASE]	= 0x280,
494d267aefcSFlorian Fainelli 	[BRCMNAND_OOB_WRITE_10_BASE]	=     0,
495d267aefcSFlorian Fainelli 	[BRCMNAND_FC_BASE]		= 0x400,
496d267aefcSFlorian Fainelli };
497d267aefcSFlorian Fainelli 
498decba6d4SFlorian Fainelli /* BRCMNAND v7.2 */
499decba6d4SFlorian Fainelli static const u16 brcmnand_regs_v72[] = {
500decba6d4SFlorian Fainelli 	[BRCMNAND_CMD_START]		=  0x04,
501decba6d4SFlorian Fainelli 	[BRCMNAND_CMD_EXT_ADDRESS]	=  0x08,
502decba6d4SFlorian Fainelli 	[BRCMNAND_CMD_ADDRESS]		=  0x0c,
503decba6d4SFlorian Fainelli 	[BRCMNAND_INTFC_STATUS]		=  0x14,
504decba6d4SFlorian Fainelli 	[BRCMNAND_CS_SELECT]		=  0x18,
505decba6d4SFlorian Fainelli 	[BRCMNAND_CS_XOR]		=  0x1c,
506decba6d4SFlorian Fainelli 	[BRCMNAND_LL_OP]		=  0x20,
507decba6d4SFlorian Fainelli 	[BRCMNAND_CS0_BASE]		=  0x50,
508decba6d4SFlorian Fainelli 	[BRCMNAND_CS1_BASE]		=     0,
509decba6d4SFlorian Fainelli 	[BRCMNAND_CORR_THRESHOLD]	=  0xdc,
510decba6d4SFlorian Fainelli 	[BRCMNAND_CORR_THRESHOLD_EXT]	=  0xe0,
511decba6d4SFlorian Fainelli 	[BRCMNAND_UNCORR_COUNT]		=  0xfc,
512decba6d4SFlorian Fainelli 	[BRCMNAND_CORR_COUNT]		= 0x100,
513decba6d4SFlorian Fainelli 	[BRCMNAND_CORR_EXT_ADDR]	= 0x10c,
514decba6d4SFlorian Fainelli 	[BRCMNAND_CORR_ADDR]		= 0x110,
515decba6d4SFlorian Fainelli 	[BRCMNAND_UNCORR_EXT_ADDR]	= 0x114,
516decba6d4SFlorian Fainelli 	[BRCMNAND_UNCORR_ADDR]		= 0x118,
517decba6d4SFlorian Fainelli 	[BRCMNAND_SEMAPHORE]		= 0x150,
518decba6d4SFlorian Fainelli 	[BRCMNAND_ID]			= 0x194,
519decba6d4SFlorian Fainelli 	[BRCMNAND_ID_EXT]		= 0x198,
520decba6d4SFlorian Fainelli 	[BRCMNAND_LL_RDATA]		= 0x19c,
521decba6d4SFlorian Fainelli 	[BRCMNAND_OOB_READ_BASE]	= 0x200,
522decba6d4SFlorian Fainelli 	[BRCMNAND_OOB_READ_10_BASE]	=     0,
523decba6d4SFlorian Fainelli 	[BRCMNAND_OOB_WRITE_BASE]	= 0x400,
524decba6d4SFlorian Fainelli 	[BRCMNAND_OOB_WRITE_10_BASE]	=     0,
525decba6d4SFlorian Fainelli 	[BRCMNAND_FC_BASE]		= 0x600,
526decba6d4SFlorian Fainelli };
527decba6d4SFlorian Fainelli 
52827c5b17cSBrian Norris enum brcmnand_cs_reg {
52927c5b17cSBrian Norris 	BRCMNAND_CS_CFG_EXT = 0,
53027c5b17cSBrian Norris 	BRCMNAND_CS_CFG,
53127c5b17cSBrian Norris 	BRCMNAND_CS_ACC_CONTROL,
53227c5b17cSBrian Norris 	BRCMNAND_CS_TIMING1,
53327c5b17cSBrian Norris 	BRCMNAND_CS_TIMING2,
53427c5b17cSBrian Norris };
53527c5b17cSBrian Norris 
53627c5b17cSBrian Norris /* Per chip-select offsets for v7.1 */
53727c5b17cSBrian Norris static const u8 brcmnand_cs_offsets_v71[] = {
53827c5b17cSBrian Norris 	[BRCMNAND_CS_ACC_CONTROL]	= 0x00,
53927c5b17cSBrian Norris 	[BRCMNAND_CS_CFG_EXT]		= 0x04,
54027c5b17cSBrian Norris 	[BRCMNAND_CS_CFG]		= 0x08,
54127c5b17cSBrian Norris 	[BRCMNAND_CS_TIMING1]		= 0x0c,
54227c5b17cSBrian Norris 	[BRCMNAND_CS_TIMING2]		= 0x10,
54327c5b17cSBrian Norris };
54427c5b17cSBrian Norris 
54527c5b17cSBrian Norris /* Per chip-select offsets for pre v7.1, except CS0 on <= v5.0 */
54627c5b17cSBrian Norris static const u8 brcmnand_cs_offsets[] = {
54727c5b17cSBrian Norris 	[BRCMNAND_CS_ACC_CONTROL]	= 0x00,
54827c5b17cSBrian Norris 	[BRCMNAND_CS_CFG_EXT]		= 0x04,
54927c5b17cSBrian Norris 	[BRCMNAND_CS_CFG]		= 0x04,
55027c5b17cSBrian Norris 	[BRCMNAND_CS_TIMING1]		= 0x08,
55127c5b17cSBrian Norris 	[BRCMNAND_CS_TIMING2]		= 0x0c,
55227c5b17cSBrian Norris };
55327c5b17cSBrian Norris 
55427c5b17cSBrian Norris /* Per chip-select offset for <= v5.0 on CS0 only */
55527c5b17cSBrian Norris static const u8 brcmnand_cs_offsets_cs0[] = {
55627c5b17cSBrian Norris 	[BRCMNAND_CS_ACC_CONTROL]	= 0x00,
55727c5b17cSBrian Norris 	[BRCMNAND_CS_CFG_EXT]		= 0x08,
55827c5b17cSBrian Norris 	[BRCMNAND_CS_CFG]		= 0x08,
55927c5b17cSBrian Norris 	[BRCMNAND_CS_TIMING1]		= 0x10,
56027c5b17cSBrian Norris 	[BRCMNAND_CS_TIMING2]		= 0x14,
56127c5b17cSBrian Norris };
56227c5b17cSBrian Norris 
5633f06d2a9SBrian Norris /*
5643f06d2a9SBrian Norris  * Bitfields for the CFG and CFG_EXT registers. Pre-v7.1 controllers only had
5653f06d2a9SBrian Norris  * one config register, but once the bitfields overflowed, newer controllers
5663f06d2a9SBrian Norris  * (v7.1 and newer) added a CFG_EXT register and shuffled a few fields around.
5673f06d2a9SBrian Norris  */
5683f06d2a9SBrian Norris enum {
5693f06d2a9SBrian Norris 	CFG_BLK_ADR_BYTES_SHIFT		= 8,
5703f06d2a9SBrian Norris 	CFG_COL_ADR_BYTES_SHIFT		= 12,
5713f06d2a9SBrian Norris 	CFG_FUL_ADR_BYTES_SHIFT		= 16,
5723f06d2a9SBrian Norris 	CFG_BUS_WIDTH_SHIFT		= 23,
5733f06d2a9SBrian Norris 	CFG_BUS_WIDTH			= BIT(CFG_BUS_WIDTH_SHIFT),
5743f06d2a9SBrian Norris 	CFG_DEVICE_SIZE_SHIFT		= 24,
5753f06d2a9SBrian Norris 
5767e7c7df5SÁlvaro Fernández Rojas 	/* Only for v2.1 */
5777e7c7df5SÁlvaro Fernández Rojas 	CFG_PAGE_SIZE_SHIFT_v2_1	= 30,
5787e7c7df5SÁlvaro Fernández Rojas 
5793f06d2a9SBrian Norris 	/* Only for pre-v7.1 (with no CFG_EXT register) */
5803f06d2a9SBrian Norris 	CFG_PAGE_SIZE_SHIFT		= 20,
5813f06d2a9SBrian Norris 	CFG_BLK_SIZE_SHIFT		= 28,
5823f06d2a9SBrian Norris 
5833f06d2a9SBrian Norris 	/* Only for v7.1+ (with CFG_EXT register) */
5843f06d2a9SBrian Norris 	CFG_EXT_PAGE_SIZE_SHIFT		= 0,
5853f06d2a9SBrian Norris 	CFG_EXT_BLK_SIZE_SHIFT		= 4,
5863f06d2a9SBrian Norris };
5873f06d2a9SBrian Norris 
58827c5b17cSBrian Norris /* BRCMNAND_INTFC_STATUS */
58927c5b17cSBrian Norris enum {
59027c5b17cSBrian Norris 	INTFC_FLASH_STATUS		= GENMASK(7, 0),
59127c5b17cSBrian Norris 
59227c5b17cSBrian Norris 	INTFC_ERASED			= BIT(27),
59327c5b17cSBrian Norris 	INTFC_OOB_VALID			= BIT(28),
59427c5b17cSBrian Norris 	INTFC_CACHE_VALID		= BIT(29),
59527c5b17cSBrian Norris 	INTFC_FLASH_READY		= BIT(30),
59627c5b17cSBrian Norris 	INTFC_CTLR_READY		= BIT(31),
59727c5b17cSBrian Norris };
59827c5b17cSBrian Norris 
59925f97138SFlorian Fainelli static inline bool brcmnand_non_mmio_ops(struct brcmnand_controller *ctrl)
60025f97138SFlorian Fainelli {
601feca4cc4SFlorian Fainelli #if IS_ENABLED(CONFIG_MTD_NAND_BRCMNAND_BCMA)
60225f97138SFlorian Fainelli 	return static_branch_unlikely(&brcmnand_soc_has_ops_key);
603feca4cc4SFlorian Fainelli #else
604feca4cc4SFlorian Fainelli 	return false;
605feca4cc4SFlorian Fainelli #endif
60625f97138SFlorian Fainelli }
60725f97138SFlorian Fainelli 
60827c5b17cSBrian Norris static inline u32 nand_readreg(struct brcmnand_controller *ctrl, u32 offs)
60927c5b17cSBrian Norris {
61025f97138SFlorian Fainelli 	if (brcmnand_non_mmio_ops(ctrl))
61125f97138SFlorian Fainelli 		return brcmnand_soc_read(ctrl->soc, offs);
61227c5b17cSBrian Norris 	return brcmnand_readl(ctrl->nand_base + offs);
61327c5b17cSBrian Norris }
61427c5b17cSBrian Norris 
61527c5b17cSBrian Norris static inline void nand_writereg(struct brcmnand_controller *ctrl, u32 offs,
61627c5b17cSBrian Norris 				 u32 val)
61727c5b17cSBrian Norris {
61825f97138SFlorian Fainelli 	if (brcmnand_non_mmio_ops(ctrl))
61925f97138SFlorian Fainelli 		brcmnand_soc_write(ctrl->soc, val, offs);
62025f97138SFlorian Fainelli 	else
62127c5b17cSBrian Norris 		brcmnand_writel(val, ctrl->nand_base + offs);
62227c5b17cSBrian Norris }
62327c5b17cSBrian Norris 
62427c5b17cSBrian Norris static int brcmnand_revision_init(struct brcmnand_controller *ctrl)
62527c5b17cSBrian Norris {
62627c5b17cSBrian Norris 	static const unsigned int block_sizes_v6[] = { 8, 16, 128, 256, 512, 1024, 2048, 0 };
62727c5b17cSBrian Norris 	static const unsigned int block_sizes_v4[] = { 16, 128, 8, 512, 256, 1024, 2048, 0 };
6287e7c7df5SÁlvaro Fernández Rojas 	static const unsigned int block_sizes_v2_2[] = { 16, 128, 8, 512, 256, 0 };
6297e7c7df5SÁlvaro Fernández Rojas 	static const unsigned int block_sizes_v2_1[] = { 16, 128, 8, 512, 0 };
630eeeac9cbSÁlvaro Fernández Rojas 	static const unsigned int page_sizes_v3_4[] = { 512, 2048, 4096, 8192, 0 };
6317e7c7df5SÁlvaro Fernández Rojas 	static const unsigned int page_sizes_v2_2[] = { 512, 2048, 4096, 0 };
6327e7c7df5SÁlvaro Fernández Rojas 	static const unsigned int page_sizes_v2_1[] = { 512, 2048, 0 };
63327c5b17cSBrian Norris 
63427c5b17cSBrian Norris 	ctrl->nand_version = nand_readreg(ctrl, 0) & 0xffff;
63527c5b17cSBrian Norris 
6367e7c7df5SÁlvaro Fernández Rojas 	/* Only support v2.1+ */
6377e7c7df5SÁlvaro Fernández Rojas 	if (ctrl->nand_version < 0x0201) {
63827c5b17cSBrian Norris 		dev_err(ctrl->dev, "version %#x not supported\n",
63927c5b17cSBrian Norris 			ctrl->nand_version);
64027c5b17cSBrian Norris 		return -ENODEV;
64127c5b17cSBrian Norris 	}
64227c5b17cSBrian Norris 
64327c5b17cSBrian Norris 	/* Register offsets */
644decba6d4SFlorian Fainelli 	if (ctrl->nand_version >= 0x0702)
645decba6d4SFlorian Fainelli 		ctrl->reg_offsets = brcmnand_regs_v72;
6460c06da57SKamal Dasu 	else if (ctrl->nand_version == 0x0701)
647d267aefcSFlorian Fainelli 		ctrl->reg_offsets = brcmnand_regs_v71;
648d267aefcSFlorian Fainelli 	else if (ctrl->nand_version >= 0x0600)
64927c5b17cSBrian Norris 		ctrl->reg_offsets = brcmnand_regs_v60;
65027c5b17cSBrian Norris 	else if (ctrl->nand_version >= 0x0500)
65127c5b17cSBrian Norris 		ctrl->reg_offsets = brcmnand_regs_v50;
6524fd63909SÁlvaro Fernández Rojas 	else if (ctrl->nand_version >= 0x0303)
6534fd63909SÁlvaro Fernández Rojas 		ctrl->reg_offsets = brcmnand_regs_v33;
6547e7c7df5SÁlvaro Fernández Rojas 	else if (ctrl->nand_version >= 0x0201)
6557e7c7df5SÁlvaro Fernández Rojas 		ctrl->reg_offsets = brcmnand_regs_v21;
65627c5b17cSBrian Norris 
65727c5b17cSBrian Norris 	/* Chip-select stride */
65827c5b17cSBrian Norris 	if (ctrl->nand_version >= 0x0701)
65927c5b17cSBrian Norris 		ctrl->reg_spacing = 0x14;
66027c5b17cSBrian Norris 	else
66127c5b17cSBrian Norris 		ctrl->reg_spacing = 0x10;
66227c5b17cSBrian Norris 
66327c5b17cSBrian Norris 	/* Per chip-select registers */
66427c5b17cSBrian Norris 	if (ctrl->nand_version >= 0x0701) {
66527c5b17cSBrian Norris 		ctrl->cs_offsets = brcmnand_cs_offsets_v71;
66627c5b17cSBrian Norris 	} else {
66727c5b17cSBrian Norris 		ctrl->cs_offsets = brcmnand_cs_offsets;
66827c5b17cSBrian Norris 
6693d3fb3c5SÁlvaro Fernández Rojas 		/* v3.3-5.0 have a different CS0 offset layout */
6703d3fb3c5SÁlvaro Fernández Rojas 		if (ctrl->nand_version >= 0x0303 &&
6713d3fb3c5SÁlvaro Fernández Rojas 		    ctrl->nand_version <= 0x0500)
67227c5b17cSBrian Norris 			ctrl->cs0_offsets = brcmnand_cs_offsets_cs0;
67327c5b17cSBrian Norris 	}
67427c5b17cSBrian Norris 
67527c5b17cSBrian Norris 	/* Page / block sizes */
67627c5b17cSBrian Norris 	if (ctrl->nand_version >= 0x0701) {
67727c5b17cSBrian Norris 		/* >= v7.1 use nice power-of-2 values! */
67827c5b17cSBrian Norris 		ctrl->max_page_size = 16 * 1024;
67927c5b17cSBrian Norris 		ctrl->max_block_size = 2 * 1024 * 1024;
68027c5b17cSBrian Norris 	} else {
6817e7c7df5SÁlvaro Fernández Rojas 		if (ctrl->nand_version >= 0x0304)
682eeeac9cbSÁlvaro Fernández Rojas 			ctrl->page_sizes = page_sizes_v3_4;
6837e7c7df5SÁlvaro Fernández Rojas 		else if (ctrl->nand_version >= 0x0202)
6847e7c7df5SÁlvaro Fernández Rojas 			ctrl->page_sizes = page_sizes_v2_2;
6857e7c7df5SÁlvaro Fernández Rojas 		else
6867e7c7df5SÁlvaro Fernández Rojas 			ctrl->page_sizes = page_sizes_v2_1;
6877e7c7df5SÁlvaro Fernández Rojas 
6887e7c7df5SÁlvaro Fernández Rojas 		if (ctrl->nand_version >= 0x0202)
6897e7c7df5SÁlvaro Fernández Rojas 			ctrl->page_size_shift = CFG_PAGE_SIZE_SHIFT;
6907e7c7df5SÁlvaro Fernández Rojas 		else
6917e7c7df5SÁlvaro Fernández Rojas 			ctrl->page_size_shift = CFG_PAGE_SIZE_SHIFT_v2_1;
6927e7c7df5SÁlvaro Fernández Rojas 
69327c5b17cSBrian Norris 		if (ctrl->nand_version >= 0x0600)
69427c5b17cSBrian Norris 			ctrl->block_sizes = block_sizes_v6;
6957e7c7df5SÁlvaro Fernández Rojas 		else if (ctrl->nand_version >= 0x0400)
69627c5b17cSBrian Norris 			ctrl->block_sizes = block_sizes_v4;
6977e7c7df5SÁlvaro Fernández Rojas 		else if (ctrl->nand_version >= 0x0202)
6987e7c7df5SÁlvaro Fernández Rojas 			ctrl->block_sizes = block_sizes_v2_2;
6997e7c7df5SÁlvaro Fernández Rojas 		else
7007e7c7df5SÁlvaro Fernández Rojas 			ctrl->block_sizes = block_sizes_v2_1;
70127c5b17cSBrian Norris 
70227c5b17cSBrian Norris 		if (ctrl->nand_version < 0x0400) {
7037e7c7df5SÁlvaro Fernández Rojas 			if (ctrl->nand_version < 0x0202)
7047e7c7df5SÁlvaro Fernández Rojas 				ctrl->max_page_size = 2048;
7057e7c7df5SÁlvaro Fernández Rojas 			else
70627c5b17cSBrian Norris 				ctrl->max_page_size = 4096;
70727c5b17cSBrian Norris 			ctrl->max_block_size = 512 * 1024;
70827c5b17cSBrian Norris 		}
70927c5b17cSBrian Norris 	}
71027c5b17cSBrian Norris 
71127c5b17cSBrian Norris 	/* Maximum spare area sector size (per 512B) */
7120c06da57SKamal Dasu 	if (ctrl->nand_version == 0x0702)
713decba6d4SFlorian Fainelli 		ctrl->max_oob = 128;
714decba6d4SFlorian Fainelli 	else if (ctrl->nand_version >= 0x0600)
71527c5b17cSBrian Norris 		ctrl->max_oob = 64;
71627c5b17cSBrian Norris 	else if (ctrl->nand_version >= 0x0500)
71727c5b17cSBrian Norris 		ctrl->max_oob = 32;
71827c5b17cSBrian Norris 	else
71927c5b17cSBrian Norris 		ctrl->max_oob = 16;
72027c5b17cSBrian Norris 
72127c5b17cSBrian Norris 	/* v6.0 and newer (except v6.1) have prefetch support */
72227c5b17cSBrian Norris 	if (ctrl->nand_version >= 0x0600 && ctrl->nand_version != 0x0601)
72327c5b17cSBrian Norris 		ctrl->features |= BRCMNAND_HAS_PREFETCH;
72427c5b17cSBrian Norris 
72527c5b17cSBrian Norris 	/*
72627c5b17cSBrian Norris 	 * v6.x has cache mode, but it's implemented differently. Ignore it for
72727c5b17cSBrian Norris 	 * now.
72827c5b17cSBrian Norris 	 */
72927c5b17cSBrian Norris 	if (ctrl->nand_version >= 0x0700)
73027c5b17cSBrian Norris 		ctrl->features |= BRCMNAND_HAS_CACHE_MODE;
73127c5b17cSBrian Norris 
73227c5b17cSBrian Norris 	if (ctrl->nand_version >= 0x0500)
73327c5b17cSBrian Norris 		ctrl->features |= BRCMNAND_HAS_1K_SECTORS;
73427c5b17cSBrian Norris 
73527c5b17cSBrian Norris 	if (ctrl->nand_version >= 0x0700)
73627c5b17cSBrian Norris 		ctrl->features |= BRCMNAND_HAS_WP;
73727c5b17cSBrian Norris 	else if (of_property_read_bool(ctrl->dev->of_node, "brcm,nand-has-wp"))
73827c5b17cSBrian Norris 		ctrl->features |= BRCMNAND_HAS_WP;
73927c5b17cSBrian Norris 
74027c5b17cSBrian Norris 	return 0;
74127c5b17cSBrian Norris }
74227c5b17cSBrian Norris 
7430c06da57SKamal Dasu static void brcmnand_flash_dma_revision_init(struct brcmnand_controller *ctrl)
7440c06da57SKamal Dasu {
7450c06da57SKamal Dasu 	/* flash_dma register offsets */
7460c06da57SKamal Dasu 	if (ctrl->nand_version >= 0x0703)
7470c06da57SKamal Dasu 		ctrl->flash_dma_offsets = flash_dma_regs_v4;
74883156c1cSKamal Dasu 	else if (ctrl->nand_version == 0x0602)
74983156c1cSKamal Dasu 		ctrl->flash_dma_offsets = flash_dma_regs_v0;
7500c06da57SKamal Dasu 	else
7510c06da57SKamal Dasu 		ctrl->flash_dma_offsets = flash_dma_regs_v1;
7520c06da57SKamal Dasu }
7530c06da57SKamal Dasu 
75427c5b17cSBrian Norris static inline u32 brcmnand_read_reg(struct brcmnand_controller *ctrl,
75527c5b17cSBrian Norris 		enum brcmnand_reg reg)
75627c5b17cSBrian Norris {
75727c5b17cSBrian Norris 	u16 offs = ctrl->reg_offsets[reg];
75827c5b17cSBrian Norris 
75927c5b17cSBrian Norris 	if (offs)
76027c5b17cSBrian Norris 		return nand_readreg(ctrl, offs);
76127c5b17cSBrian Norris 	else
76227c5b17cSBrian Norris 		return 0;
76327c5b17cSBrian Norris }
76427c5b17cSBrian Norris 
76527c5b17cSBrian Norris static inline void brcmnand_write_reg(struct brcmnand_controller *ctrl,
76627c5b17cSBrian Norris 				      enum brcmnand_reg reg, u32 val)
76727c5b17cSBrian Norris {
76827c5b17cSBrian Norris 	u16 offs = ctrl->reg_offsets[reg];
76927c5b17cSBrian Norris 
77027c5b17cSBrian Norris 	if (offs)
77127c5b17cSBrian Norris 		nand_writereg(ctrl, offs, val);
77227c5b17cSBrian Norris }
77327c5b17cSBrian Norris 
77427c5b17cSBrian Norris static inline void brcmnand_rmw_reg(struct brcmnand_controller *ctrl,
77527c5b17cSBrian Norris 				    enum brcmnand_reg reg, u32 mask, unsigned
77627c5b17cSBrian Norris 				    int shift, u32 val)
77727c5b17cSBrian Norris {
77827c5b17cSBrian Norris 	u32 tmp = brcmnand_read_reg(ctrl, reg);
77927c5b17cSBrian Norris 
78027c5b17cSBrian Norris 	tmp &= ~mask;
78127c5b17cSBrian Norris 	tmp |= val << shift;
78227c5b17cSBrian Norris 	brcmnand_write_reg(ctrl, reg, tmp);
78327c5b17cSBrian Norris }
78427c5b17cSBrian Norris 
78527c5b17cSBrian Norris static inline u32 brcmnand_read_fc(struct brcmnand_controller *ctrl, int word)
78627c5b17cSBrian Norris {
78725f97138SFlorian Fainelli 	if (brcmnand_non_mmio_ops(ctrl))
78825f97138SFlorian Fainelli 		return brcmnand_soc_read(ctrl->soc, BRCMNAND_NON_MMIO_FC_ADDR);
78927c5b17cSBrian Norris 	return __raw_readl(ctrl->nand_fc + word * 4);
79027c5b17cSBrian Norris }
79127c5b17cSBrian Norris 
79227c5b17cSBrian Norris static inline void brcmnand_write_fc(struct brcmnand_controller *ctrl,
79327c5b17cSBrian Norris 				     int word, u32 val)
79427c5b17cSBrian Norris {
79525f97138SFlorian Fainelli 	if (brcmnand_non_mmio_ops(ctrl))
79625f97138SFlorian Fainelli 		brcmnand_soc_write(ctrl->soc, val, BRCMNAND_NON_MMIO_FC_ADDR);
79725f97138SFlorian Fainelli 	else
79827c5b17cSBrian Norris 		__raw_writel(val, ctrl->nand_fc + word * 4);
79927c5b17cSBrian Norris }
80027c5b17cSBrian Norris 
801a5d53ad2SKamal Dasu static inline void edu_writel(struct brcmnand_controller *ctrl,
802a5d53ad2SKamal Dasu 			      enum edu_reg reg, u32 val)
803a5d53ad2SKamal Dasu {
804a5d53ad2SKamal Dasu 	u16 offs = ctrl->edu_offsets[reg];
805a5d53ad2SKamal Dasu 
806a5d53ad2SKamal Dasu 	brcmnand_writel(val, ctrl->edu_base + offs);
807a5d53ad2SKamal Dasu }
808a5d53ad2SKamal Dasu 
809a5d53ad2SKamal Dasu static inline u32 edu_readl(struct brcmnand_controller *ctrl,
810a5d53ad2SKamal Dasu 			    enum edu_reg reg)
811a5d53ad2SKamal Dasu {
812a5d53ad2SKamal Dasu 	u16 offs = ctrl->edu_offsets[reg];
813a5d53ad2SKamal Dasu 
814a5d53ad2SKamal Dasu 	return brcmnand_readl(ctrl->edu_base + offs);
815a5d53ad2SKamal Dasu }
816a5d53ad2SKamal Dasu 
8173c7c1e45SKamal Dasu static void brcmnand_clear_ecc_addr(struct brcmnand_controller *ctrl)
8183c7c1e45SKamal Dasu {
8193c7c1e45SKamal Dasu 
8203c7c1e45SKamal Dasu 	/* Clear error addresses */
8213c7c1e45SKamal Dasu 	brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_ADDR, 0);
8223c7c1e45SKamal Dasu 	brcmnand_write_reg(ctrl, BRCMNAND_CORR_ADDR, 0);
8233c7c1e45SKamal Dasu 	brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_EXT_ADDR, 0);
8243c7c1e45SKamal Dasu 	brcmnand_write_reg(ctrl, BRCMNAND_CORR_EXT_ADDR, 0);
8253c7c1e45SKamal Dasu }
8263c7c1e45SKamal Dasu 
8273c7c1e45SKamal Dasu static u64 brcmnand_get_uncorrecc_addr(struct brcmnand_controller *ctrl)
8283c7c1e45SKamal Dasu {
8293c7c1e45SKamal Dasu 	u64 err_addr;
8303c7c1e45SKamal Dasu 
8313c7c1e45SKamal Dasu 	err_addr = brcmnand_read_reg(ctrl, BRCMNAND_UNCORR_ADDR);
8323c7c1e45SKamal Dasu 	err_addr |= ((u64)(brcmnand_read_reg(ctrl,
8333c7c1e45SKamal Dasu 					     BRCMNAND_UNCORR_EXT_ADDR)
8343c7c1e45SKamal Dasu 					     & 0xffff) << 32);
8353c7c1e45SKamal Dasu 
8363c7c1e45SKamal Dasu 	return err_addr;
8373c7c1e45SKamal Dasu }
8383c7c1e45SKamal Dasu 
8393c7c1e45SKamal Dasu static u64 brcmnand_get_correcc_addr(struct brcmnand_controller *ctrl)
8403c7c1e45SKamal Dasu {
8413c7c1e45SKamal Dasu 	u64 err_addr;
8423c7c1e45SKamal Dasu 
8433c7c1e45SKamal Dasu 	err_addr = brcmnand_read_reg(ctrl, BRCMNAND_CORR_ADDR);
8443c7c1e45SKamal Dasu 	err_addr |= ((u64)(brcmnand_read_reg(ctrl,
8453c7c1e45SKamal Dasu 					     BRCMNAND_CORR_EXT_ADDR)
8463c7c1e45SKamal Dasu 					     & 0xffff) << 32);
8473c7c1e45SKamal Dasu 
8483c7c1e45SKamal Dasu 	return err_addr;
8493c7c1e45SKamal Dasu }
8503c7c1e45SKamal Dasu 
8513c7c1e45SKamal Dasu static void brcmnand_set_cmd_addr(struct mtd_info *mtd, u64 addr)
8523c7c1e45SKamal Dasu {
8533c7c1e45SKamal Dasu 	struct nand_chip *chip =  mtd_to_nand(mtd);
8543c7c1e45SKamal Dasu 	struct brcmnand_host *host = nand_get_controller_data(chip);
8553c7c1e45SKamal Dasu 	struct brcmnand_controller *ctrl = host->ctrl;
8563c7c1e45SKamal Dasu 
8573c7c1e45SKamal Dasu 	brcmnand_write_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS,
8583c7c1e45SKamal Dasu 			   (host->cs << 16) | ((addr >> 32) & 0xffff));
8593c7c1e45SKamal Dasu 	(void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS);
8603c7c1e45SKamal Dasu 	brcmnand_write_reg(ctrl, BRCMNAND_CMD_ADDRESS,
8613c7c1e45SKamal Dasu 			   lower_32_bits(addr));
8623c7c1e45SKamal Dasu 	(void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS);
8633c7c1e45SKamal Dasu }
8643c7c1e45SKamal Dasu 
86527c5b17cSBrian Norris static inline u16 brcmnand_cs_offset(struct brcmnand_controller *ctrl, int cs,
86627c5b17cSBrian Norris 				     enum brcmnand_cs_reg reg)
86727c5b17cSBrian Norris {
86827c5b17cSBrian Norris 	u16 offs_cs0 = ctrl->reg_offsets[BRCMNAND_CS0_BASE];
86927c5b17cSBrian Norris 	u16 offs_cs1 = ctrl->reg_offsets[BRCMNAND_CS1_BASE];
87027c5b17cSBrian Norris 	u8 cs_offs;
87127c5b17cSBrian Norris 
87227c5b17cSBrian Norris 	if (cs == 0 && ctrl->cs0_offsets)
87327c5b17cSBrian Norris 		cs_offs = ctrl->cs0_offsets[reg];
87427c5b17cSBrian Norris 	else
87527c5b17cSBrian Norris 		cs_offs = ctrl->cs_offsets[reg];
87627c5b17cSBrian Norris 
87727c5b17cSBrian Norris 	if (cs && offs_cs1)
87827c5b17cSBrian Norris 		return offs_cs1 + (cs - 1) * ctrl->reg_spacing + cs_offs;
87927c5b17cSBrian Norris 
88027c5b17cSBrian Norris 	return offs_cs0 + cs * ctrl->reg_spacing + cs_offs;
88127c5b17cSBrian Norris }
88227c5b17cSBrian Norris 
88327c5b17cSBrian Norris static inline u32 brcmnand_count_corrected(struct brcmnand_controller *ctrl)
88427c5b17cSBrian Norris {
88527c5b17cSBrian Norris 	if (ctrl->nand_version < 0x0600)
88627c5b17cSBrian Norris 		return 1;
88727c5b17cSBrian Norris 	return brcmnand_read_reg(ctrl, BRCMNAND_CORR_COUNT);
88827c5b17cSBrian Norris }
88927c5b17cSBrian Norris 
89027c5b17cSBrian Norris static void brcmnand_wr_corr_thresh(struct brcmnand_host *host, u8 val)
89127c5b17cSBrian Norris {
89227c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
89327c5b17cSBrian Norris 	unsigned int shift = 0, bits;
89427c5b17cSBrian Norris 	enum brcmnand_reg reg = BRCMNAND_CORR_THRESHOLD;
89527c5b17cSBrian Norris 	int cs = host->cs;
89627c5b17cSBrian Norris 
8977e7c7df5SÁlvaro Fernández Rojas 	if (!ctrl->reg_offsets[reg])
8987e7c7df5SÁlvaro Fernández Rojas 		return;
8997e7c7df5SÁlvaro Fernández Rojas 
9000c06da57SKamal Dasu 	if (ctrl->nand_version == 0x0702)
901decba6d4SFlorian Fainelli 		bits = 7;
902decba6d4SFlorian Fainelli 	else if (ctrl->nand_version >= 0x0600)
90327c5b17cSBrian Norris 		bits = 6;
90427c5b17cSBrian Norris 	else if (ctrl->nand_version >= 0x0500)
90527c5b17cSBrian Norris 		bits = 5;
90627c5b17cSBrian Norris 	else
90727c5b17cSBrian Norris 		bits = 4;
90827c5b17cSBrian Norris 
909decba6d4SFlorian Fainelli 	if (ctrl->nand_version >= 0x0702) {
910decba6d4SFlorian Fainelli 		if (cs >= 4)
911decba6d4SFlorian Fainelli 			reg = BRCMNAND_CORR_THRESHOLD_EXT;
912decba6d4SFlorian Fainelli 		shift = (cs % 4) * bits;
913decba6d4SFlorian Fainelli 	} else if (ctrl->nand_version >= 0x0600) {
91427c5b17cSBrian Norris 		if (cs >= 5)
91527c5b17cSBrian Norris 			reg = BRCMNAND_CORR_THRESHOLD_EXT;
91627c5b17cSBrian Norris 		shift = (cs % 5) * bits;
91727c5b17cSBrian Norris 	}
91827c5b17cSBrian Norris 	brcmnand_rmw_reg(ctrl, reg, (bits - 1) << shift, shift, val);
91927c5b17cSBrian Norris }
92027c5b17cSBrian Norris 
92127c5b17cSBrian Norris static inline int brcmnand_cmd_shift(struct brcmnand_controller *ctrl)
92227c5b17cSBrian Norris {
9235abd37f6SFlorian Fainelli 	/* Kludge for the BCMA-based NAND controller which does not actually
9245abd37f6SFlorian Fainelli 	 * shift the command
9255abd37f6SFlorian Fainelli 	 */
9265abd37f6SFlorian Fainelli 	if (ctrl->nand_version == 0x0304 && brcmnand_non_mmio_ops(ctrl))
9275abd37f6SFlorian Fainelli 		return 0;
9285abd37f6SFlorian Fainelli 
929269ecf03SFlorian Fainelli 	if (ctrl->nand_version < 0x0602)
93027c5b17cSBrian Norris 		return 24;
93127c5b17cSBrian Norris 	return 0;
93227c5b17cSBrian Norris }
93327c5b17cSBrian Norris 
93427c5b17cSBrian Norris /***********************************************************************
93527c5b17cSBrian Norris  * NAND ACC CONTROL bitfield
93627c5b17cSBrian Norris  *
93727c5b17cSBrian Norris  * Some bits have remained constant throughout hardware revision, while
93827c5b17cSBrian Norris  * others have shifted around.
93927c5b17cSBrian Norris  ***********************************************************************/
94027c5b17cSBrian Norris 
94127c5b17cSBrian Norris /* Constant for all versions (where supported) */
94227c5b17cSBrian Norris enum {
94327c5b17cSBrian Norris 	/* See BRCMNAND_HAS_CACHE_MODE */
94427c5b17cSBrian Norris 	ACC_CONTROL_CACHE_MODE				= BIT(22),
94527c5b17cSBrian Norris 
94627c5b17cSBrian Norris 	/* See BRCMNAND_HAS_PREFETCH */
94727c5b17cSBrian Norris 	ACC_CONTROL_PREFETCH				= BIT(23),
94827c5b17cSBrian Norris 
94927c5b17cSBrian Norris 	ACC_CONTROL_PAGE_HIT				= BIT(24),
95027c5b17cSBrian Norris 	ACC_CONTROL_WR_PREEMPT				= BIT(25),
95127c5b17cSBrian Norris 	ACC_CONTROL_PARTIAL_PAGE			= BIT(26),
95227c5b17cSBrian Norris 	ACC_CONTROL_RD_ERASED				= BIT(27),
95327c5b17cSBrian Norris 	ACC_CONTROL_FAST_PGM_RDIN			= BIT(28),
95427c5b17cSBrian Norris 	ACC_CONTROL_WR_ECC				= BIT(30),
95527c5b17cSBrian Norris 	ACC_CONTROL_RD_ECC				= BIT(31),
95627c5b17cSBrian Norris };
95727c5b17cSBrian Norris 
95827c5b17cSBrian Norris static inline u32 brcmnand_spare_area_mask(struct brcmnand_controller *ctrl)
95927c5b17cSBrian Norris {
9600c06da57SKamal Dasu 	if (ctrl->nand_version == 0x0702)
961decba6d4SFlorian Fainelli 		return GENMASK(7, 0);
962decba6d4SFlorian Fainelli 	else if (ctrl->nand_version >= 0x0600)
96327c5b17cSBrian Norris 		return GENMASK(6, 0);
9647e7c7df5SÁlvaro Fernández Rojas 	else if (ctrl->nand_version >= 0x0303)
96527c5b17cSBrian Norris 		return GENMASK(5, 0);
9667e7c7df5SÁlvaro Fernández Rojas 	else
9677e7c7df5SÁlvaro Fernández Rojas 		return GENMASK(4, 0);
96827c5b17cSBrian Norris }
96927c5b17cSBrian Norris 
97027c5b17cSBrian Norris #define NAND_ACC_CONTROL_ECC_SHIFT	16
971decba6d4SFlorian Fainelli #define NAND_ACC_CONTROL_ECC_EXT_SHIFT	13
97227c5b17cSBrian Norris 
97327c5b17cSBrian Norris static inline u32 brcmnand_ecc_level_mask(struct brcmnand_controller *ctrl)
97427c5b17cSBrian Norris {
97527c5b17cSBrian Norris 	u32 mask = (ctrl->nand_version >= 0x0600) ? 0x1f : 0x0f;
97627c5b17cSBrian Norris 
977decba6d4SFlorian Fainelli 	mask <<= NAND_ACC_CONTROL_ECC_SHIFT;
978decba6d4SFlorian Fainelli 
979decba6d4SFlorian Fainelli 	/* v7.2 includes additional ECC levels */
980decba6d4SFlorian Fainelli 	if (ctrl->nand_version >= 0x0702)
981decba6d4SFlorian Fainelli 		mask |= 0x7 << NAND_ACC_CONTROL_ECC_EXT_SHIFT;
982decba6d4SFlorian Fainelli 
983decba6d4SFlorian Fainelli 	return mask;
98427c5b17cSBrian Norris }
98527c5b17cSBrian Norris 
98627c5b17cSBrian Norris static void brcmnand_set_ecc_enabled(struct brcmnand_host *host, int en)
98727c5b17cSBrian Norris {
98827c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
98927c5b17cSBrian Norris 	u16 offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_ACC_CONTROL);
99027c5b17cSBrian Norris 	u32 acc_control = nand_readreg(ctrl, offs);
99127c5b17cSBrian Norris 	u32 ecc_flags = ACC_CONTROL_WR_ECC | ACC_CONTROL_RD_ECC;
99227c5b17cSBrian Norris 
99327c5b17cSBrian Norris 	if (en) {
99427c5b17cSBrian Norris 		acc_control |= ecc_flags; /* enable RD/WR ECC */
99527c5b17cSBrian Norris 		acc_control |= host->hwcfg.ecc_level
99627c5b17cSBrian Norris 			       << NAND_ACC_CONTROL_ECC_SHIFT;
99727c5b17cSBrian Norris 	} else {
99827c5b17cSBrian Norris 		acc_control &= ~ecc_flags; /* disable RD/WR ECC */
99927c5b17cSBrian Norris 		acc_control &= ~brcmnand_ecc_level_mask(ctrl);
100027c5b17cSBrian Norris 	}
100127c5b17cSBrian Norris 
100227c5b17cSBrian Norris 	nand_writereg(ctrl, offs, acc_control);
100327c5b17cSBrian Norris }
100427c5b17cSBrian Norris 
100527c5b17cSBrian Norris static inline int brcmnand_sector_1k_shift(struct brcmnand_controller *ctrl)
100627c5b17cSBrian Norris {
1007decba6d4SFlorian Fainelli 	if (ctrl->nand_version >= 0x0702)
1008decba6d4SFlorian Fainelli 		return 9;
1009decba6d4SFlorian Fainelli 	else if (ctrl->nand_version >= 0x0600)
101027c5b17cSBrian Norris 		return 7;
101127c5b17cSBrian Norris 	else if (ctrl->nand_version >= 0x0500)
101227c5b17cSBrian Norris 		return 6;
101327c5b17cSBrian Norris 	else
101427c5b17cSBrian Norris 		return -1;
101527c5b17cSBrian Norris }
101627c5b17cSBrian Norris 
101727c5b17cSBrian Norris static int brcmnand_get_sector_size_1k(struct brcmnand_host *host)
101827c5b17cSBrian Norris {
101927c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
102027c5b17cSBrian Norris 	int shift = brcmnand_sector_1k_shift(ctrl);
102127c5b17cSBrian Norris 	u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs,
102227c5b17cSBrian Norris 						  BRCMNAND_CS_ACC_CONTROL);
102327c5b17cSBrian Norris 
102427c5b17cSBrian Norris 	if (shift < 0)
102527c5b17cSBrian Norris 		return 0;
102627c5b17cSBrian Norris 
102727c5b17cSBrian Norris 	return (nand_readreg(ctrl, acc_control_offs) >> shift) & 0x1;
102827c5b17cSBrian Norris }
102927c5b17cSBrian Norris 
103027c5b17cSBrian Norris static void brcmnand_set_sector_size_1k(struct brcmnand_host *host, int val)
103127c5b17cSBrian Norris {
103227c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
103327c5b17cSBrian Norris 	int shift = brcmnand_sector_1k_shift(ctrl);
103427c5b17cSBrian Norris 	u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs,
103527c5b17cSBrian Norris 						  BRCMNAND_CS_ACC_CONTROL);
103627c5b17cSBrian Norris 	u32 tmp;
103727c5b17cSBrian Norris 
103827c5b17cSBrian Norris 	if (shift < 0)
103927c5b17cSBrian Norris 		return;
104027c5b17cSBrian Norris 
104127c5b17cSBrian Norris 	tmp = nand_readreg(ctrl, acc_control_offs);
104227c5b17cSBrian Norris 	tmp &= ~(1 << shift);
104327c5b17cSBrian Norris 	tmp |= (!!val) << shift;
104427c5b17cSBrian Norris 	nand_writereg(ctrl, acc_control_offs, tmp);
104527c5b17cSBrian Norris }
104627c5b17cSBrian Norris 
104727c5b17cSBrian Norris /***********************************************************************
104827c5b17cSBrian Norris  * CS_NAND_SELECT
104927c5b17cSBrian Norris  ***********************************************************************/
105027c5b17cSBrian Norris 
105127c5b17cSBrian Norris enum {
105227c5b17cSBrian Norris 	CS_SELECT_NAND_WP			= BIT(29),
105327c5b17cSBrian Norris 	CS_SELECT_AUTO_DEVICE_ID_CFG		= BIT(30),
105427c5b17cSBrian Norris };
105527c5b17cSBrian Norris 
10569d2ee0a6SKamal Dasu static int bcmnand_ctrl_poll_status(struct brcmnand_controller *ctrl,
10579d2ee0a6SKamal Dasu 				    u32 mask, u32 expected_val,
10589d2ee0a6SKamal Dasu 				    unsigned long timeout_ms)
10599d2ee0a6SKamal Dasu {
10609d2ee0a6SKamal Dasu 	unsigned long limit;
10619d2ee0a6SKamal Dasu 	u32 val;
10629d2ee0a6SKamal Dasu 
10639d2ee0a6SKamal Dasu 	if (!timeout_ms)
10649d2ee0a6SKamal Dasu 		timeout_ms = NAND_POLL_STATUS_TIMEOUT_MS;
10659d2ee0a6SKamal Dasu 
10669d2ee0a6SKamal Dasu 	limit = jiffies + msecs_to_jiffies(timeout_ms);
10679d2ee0a6SKamal Dasu 	do {
10689d2ee0a6SKamal Dasu 		val = brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS);
10699d2ee0a6SKamal Dasu 		if ((val & mask) == expected_val)
10709d2ee0a6SKamal Dasu 			return 0;
10719d2ee0a6SKamal Dasu 
10729d2ee0a6SKamal Dasu 		cpu_relax();
10739d2ee0a6SKamal Dasu 	} while (time_after(limit, jiffies));
10749d2ee0a6SKamal Dasu 
10759d2ee0a6SKamal Dasu 	dev_warn(ctrl->dev, "timeout on status poll (expected %x got %x)\n",
10769d2ee0a6SKamal Dasu 		 expected_val, val & mask);
10779d2ee0a6SKamal Dasu 
10789d2ee0a6SKamal Dasu 	return -ETIMEDOUT;
10799d2ee0a6SKamal Dasu }
10809d2ee0a6SKamal Dasu 
108127c5b17cSBrian Norris static inline void brcmnand_set_wp(struct brcmnand_controller *ctrl, bool en)
108227c5b17cSBrian Norris {
108327c5b17cSBrian Norris 	u32 val = en ? CS_SELECT_NAND_WP : 0;
108427c5b17cSBrian Norris 
108527c5b17cSBrian Norris 	brcmnand_rmw_reg(ctrl, BRCMNAND_CS_SELECT, CS_SELECT_NAND_WP, 0, val);
108627c5b17cSBrian Norris }
108727c5b17cSBrian Norris 
108827c5b17cSBrian Norris /***********************************************************************
108927c5b17cSBrian Norris  * Flash DMA
109027c5b17cSBrian Norris  ***********************************************************************/
109127c5b17cSBrian Norris 
109227c5b17cSBrian Norris static inline bool has_flash_dma(struct brcmnand_controller *ctrl)
109327c5b17cSBrian Norris {
109427c5b17cSBrian Norris 	return ctrl->flash_dma_base;
109527c5b17cSBrian Norris }
109627c5b17cSBrian Norris 
1097a5d53ad2SKamal Dasu static inline bool has_edu(struct brcmnand_controller *ctrl)
1098a5d53ad2SKamal Dasu {
1099a5d53ad2SKamal Dasu 	return ctrl->edu_base;
1100a5d53ad2SKamal Dasu }
1101a5d53ad2SKamal Dasu 
1102a5d53ad2SKamal Dasu static inline bool use_dma(struct brcmnand_controller *ctrl)
1103a5d53ad2SKamal Dasu {
1104a5d53ad2SKamal Dasu 	return has_flash_dma(ctrl) || has_edu(ctrl);
1105a5d53ad2SKamal Dasu }
1106a5d53ad2SKamal Dasu 
1107c1ac2dc3SKamal Dasu static inline void disable_ctrl_irqs(struct brcmnand_controller *ctrl)
1108c1ac2dc3SKamal Dasu {
1109c1ac2dc3SKamal Dasu 	if (ctrl->pio_poll_mode)
1110c1ac2dc3SKamal Dasu 		return;
1111c1ac2dc3SKamal Dasu 
1112c1ac2dc3SKamal Dasu 	if (has_flash_dma(ctrl)) {
11130e04b2ffSFlorian Fainelli 		ctrl->flash_dma_base = NULL;
1114c1ac2dc3SKamal Dasu 		disable_irq(ctrl->dma_irq);
1115c1ac2dc3SKamal Dasu 	}
1116c1ac2dc3SKamal Dasu 
1117c1ac2dc3SKamal Dasu 	disable_irq(ctrl->irq);
1118c1ac2dc3SKamal Dasu 	ctrl->pio_poll_mode = true;
1119c1ac2dc3SKamal Dasu }
1120c1ac2dc3SKamal Dasu 
112127c5b17cSBrian Norris static inline bool flash_dma_buf_ok(const void *buf)
112227c5b17cSBrian Norris {
112327c5b17cSBrian Norris 	return buf && !is_vmalloc_addr(buf) &&
112427c5b17cSBrian Norris 		likely(IS_ALIGNED((uintptr_t)buf, 4));
112527c5b17cSBrian Norris }
112627c5b17cSBrian Norris 
11270c06da57SKamal Dasu static inline void flash_dma_writel(struct brcmnand_controller *ctrl,
11280c06da57SKamal Dasu 				    enum flash_dma_reg dma_reg, u32 val)
112927c5b17cSBrian Norris {
11300c06da57SKamal Dasu 	u16 offs = ctrl->flash_dma_offsets[dma_reg];
11310c06da57SKamal Dasu 
113227c5b17cSBrian Norris 	brcmnand_writel(val, ctrl->flash_dma_base + offs);
113327c5b17cSBrian Norris }
113427c5b17cSBrian Norris 
11350c06da57SKamal Dasu static inline u32 flash_dma_readl(struct brcmnand_controller *ctrl,
11360c06da57SKamal Dasu 				  enum flash_dma_reg dma_reg)
113727c5b17cSBrian Norris {
11380c06da57SKamal Dasu 	u16 offs = ctrl->flash_dma_offsets[dma_reg];
11390c06da57SKamal Dasu 
114027c5b17cSBrian Norris 	return brcmnand_readl(ctrl->flash_dma_base + offs);
114127c5b17cSBrian Norris }
114227c5b17cSBrian Norris 
114327c5b17cSBrian Norris /* Low-level operation types: command, address, write, or read */
114427c5b17cSBrian Norris enum brcmnand_llop_type {
114527c5b17cSBrian Norris 	LL_OP_CMD,
114627c5b17cSBrian Norris 	LL_OP_ADDR,
114727c5b17cSBrian Norris 	LL_OP_WR,
114827c5b17cSBrian Norris 	LL_OP_RD,
114927c5b17cSBrian Norris };
115027c5b17cSBrian Norris 
115127c5b17cSBrian Norris /***********************************************************************
115227c5b17cSBrian Norris  * Internal support functions
115327c5b17cSBrian Norris  ***********************************************************************/
115427c5b17cSBrian Norris 
1155decba6d4SFlorian Fainelli static inline bool is_hamming_ecc(struct brcmnand_controller *ctrl,
1156decba6d4SFlorian Fainelli 				  struct brcmnand_cfg *cfg)
115727c5b17cSBrian Norris {
1158decba6d4SFlorian Fainelli 	if (ctrl->nand_version <= 0x0701)
115927c5b17cSBrian Norris 		return cfg->sector_size_1k == 0 && cfg->spare_area_size == 16 &&
116027c5b17cSBrian Norris 			cfg->ecc_level == 15;
1161decba6d4SFlorian Fainelli 	else
1162decba6d4SFlorian Fainelli 		return cfg->sector_size_1k == 0 && ((cfg->spare_area_size == 16 &&
1163decba6d4SFlorian Fainelli 			cfg->ecc_level == 15) ||
1164decba6d4SFlorian Fainelli 			(cfg->spare_area_size == 28 && cfg->ecc_level == 16));
116527c5b17cSBrian Norris }
116627c5b17cSBrian Norris 
116727c5b17cSBrian Norris /*
1168ef5eeea6SBoris Brezillon  * Set mtd->ooblayout to the appropriate mtd_ooblayout_ops given
1169ef5eeea6SBoris Brezillon  * the layout/configuration.
1170ef5eeea6SBoris Brezillon  * Returns -ERRCODE on failure.
117127c5b17cSBrian Norris  */
1172ef5eeea6SBoris Brezillon static int brcmnand_hamming_ooblayout_ecc(struct mtd_info *mtd, int section,
1173ef5eeea6SBoris Brezillon 					  struct mtd_oob_region *oobregion)
117427c5b17cSBrian Norris {
1175ef5eeea6SBoris Brezillon 	struct nand_chip *chip = mtd_to_nand(mtd);
1176ef5eeea6SBoris Brezillon 	struct brcmnand_host *host = nand_get_controller_data(chip);
117727c5b17cSBrian Norris 	struct brcmnand_cfg *cfg = &host->hwcfg;
1178ef5eeea6SBoris Brezillon 	int sas = cfg->spare_area_size << cfg->sector_size_1k;
1179ef5eeea6SBoris Brezillon 	int sectors = cfg->page_size / (512 << cfg->sector_size_1k);
118027c5b17cSBrian Norris 
1181ef5eeea6SBoris Brezillon 	if (section >= sectors)
1182ef5eeea6SBoris Brezillon 		return -ERANGE;
118327c5b17cSBrian Norris 
1184ef5eeea6SBoris Brezillon 	oobregion->offset = (section * sas) + 6;
1185ef5eeea6SBoris Brezillon 	oobregion->length = 3;
118627c5b17cSBrian Norris 
1187ef5eeea6SBoris Brezillon 	return 0;
1188ef5eeea6SBoris Brezillon }
1189ef5eeea6SBoris Brezillon 
1190ef5eeea6SBoris Brezillon static int brcmnand_hamming_ooblayout_free(struct mtd_info *mtd, int section,
1191ef5eeea6SBoris Brezillon 					   struct mtd_oob_region *oobregion)
1192ef5eeea6SBoris Brezillon {
1193ef5eeea6SBoris Brezillon 	struct nand_chip *chip = mtd_to_nand(mtd);
1194ef5eeea6SBoris Brezillon 	struct brcmnand_host *host = nand_get_controller_data(chip);
1195ef5eeea6SBoris Brezillon 	struct brcmnand_cfg *cfg = &host->hwcfg;
1196ef5eeea6SBoris Brezillon 	int sas = cfg->spare_area_size << cfg->sector_size_1k;
1197ef5eeea6SBoris Brezillon 	int sectors = cfg->page_size / (512 << cfg->sector_size_1k);
1198d00358d7SÁlvaro Fernández Rojas 	u32 next;
1199ef5eeea6SBoris Brezillon 
1200d00358d7SÁlvaro Fernández Rojas 	if (section > sectors)
1201ef5eeea6SBoris Brezillon 		return -ERANGE;
1202ef5eeea6SBoris Brezillon 
1203d00358d7SÁlvaro Fernández Rojas 	next = (section * sas);
1204d00358d7SÁlvaro Fernández Rojas 	if (section < sectors)
1205d00358d7SÁlvaro Fernández Rojas 		next += 6;
1206ef5eeea6SBoris Brezillon 
1207d00358d7SÁlvaro Fernández Rojas 	if (section) {
1208d00358d7SÁlvaro Fernández Rojas 		oobregion->offset = ((section - 1) * sas) + 9;
120927c5b17cSBrian Norris 	} else {
1210130bbde4SÁlvaro Fernández Rojas 		if (cfg->page_size > 512) {
1211d00358d7SÁlvaro Fernández Rojas 			/* Large page NAND uses first 2 bytes for BBI */
1212d00358d7SÁlvaro Fernández Rojas 			oobregion->offset = 2;
1213130bbde4SÁlvaro Fernández Rojas 		} else {
1214d00358d7SÁlvaro Fernández Rojas 			/* Small page NAND uses last byte before ECC for BBI */
1215d00358d7SÁlvaro Fernández Rojas 			oobregion->offset = 0;
1216d00358d7SÁlvaro Fernández Rojas 			next--;
121727c5b17cSBrian Norris 		}
121827c5b17cSBrian Norris 	}
121927c5b17cSBrian Norris 
1220d00358d7SÁlvaro Fernández Rojas 	oobregion->length = next - oobregion->offset;
1221f5b8aa78SBoris BREZILLON 
1222ef5eeea6SBoris Brezillon 	return 0;
1223ef5eeea6SBoris Brezillon }
1224ef5eeea6SBoris Brezillon 
1225ef5eeea6SBoris Brezillon static const struct mtd_ooblayout_ops brcmnand_hamming_ooblayout_ops = {
1226ef5eeea6SBoris Brezillon 	.ecc = brcmnand_hamming_ooblayout_ecc,
1227ef5eeea6SBoris Brezillon 	.free = brcmnand_hamming_ooblayout_free,
1228ef5eeea6SBoris Brezillon };
1229ef5eeea6SBoris Brezillon 
1230ef5eeea6SBoris Brezillon static int brcmnand_bch_ooblayout_ecc(struct mtd_info *mtd, int section,
1231ef5eeea6SBoris Brezillon 				      struct mtd_oob_region *oobregion)
1232ef5eeea6SBoris Brezillon {
1233ef5eeea6SBoris Brezillon 	struct nand_chip *chip = mtd_to_nand(mtd);
1234ef5eeea6SBoris Brezillon 	struct brcmnand_host *host = nand_get_controller_data(chip);
1235ef5eeea6SBoris Brezillon 	struct brcmnand_cfg *cfg = &host->hwcfg;
1236ef5eeea6SBoris Brezillon 	int sas = cfg->spare_area_size << cfg->sector_size_1k;
1237ef5eeea6SBoris Brezillon 	int sectors = cfg->page_size / (512 << cfg->sector_size_1k);
1238ef5eeea6SBoris Brezillon 
1239ef5eeea6SBoris Brezillon 	if (section >= sectors)
1240ef5eeea6SBoris Brezillon 		return -ERANGE;
1241ef5eeea6SBoris Brezillon 
1242917cc594SKamal Dasu 	oobregion->offset = ((section + 1) * sas) - chip->ecc.bytes;
1243ef5eeea6SBoris Brezillon 	oobregion->length = chip->ecc.bytes;
1244ef5eeea6SBoris Brezillon 
1245ef5eeea6SBoris Brezillon 	return 0;
1246ef5eeea6SBoris Brezillon }
1247ef5eeea6SBoris Brezillon 
1248ef5eeea6SBoris Brezillon static int brcmnand_bch_ooblayout_free_lp(struct mtd_info *mtd, int section,
1249ef5eeea6SBoris Brezillon 					  struct mtd_oob_region *oobregion)
1250ef5eeea6SBoris Brezillon {
1251ef5eeea6SBoris Brezillon 	struct nand_chip *chip = mtd_to_nand(mtd);
1252ef5eeea6SBoris Brezillon 	struct brcmnand_host *host = nand_get_controller_data(chip);
1253ef5eeea6SBoris Brezillon 	struct brcmnand_cfg *cfg = &host->hwcfg;
1254ef5eeea6SBoris Brezillon 	int sas = cfg->spare_area_size << cfg->sector_size_1k;
1255ef5eeea6SBoris Brezillon 	int sectors = cfg->page_size / (512 << cfg->sector_size_1k);
1256ef5eeea6SBoris Brezillon 
1257ef5eeea6SBoris Brezillon 	if (section >= sectors)
1258ef5eeea6SBoris Brezillon 		return -ERANGE;
1259ef5eeea6SBoris Brezillon 
1260ef5eeea6SBoris Brezillon 	if (sas <= chip->ecc.bytes)
1261ef5eeea6SBoris Brezillon 		return 0;
1262ef5eeea6SBoris Brezillon 
1263ef5eeea6SBoris Brezillon 	oobregion->offset = section * sas;
1264ef5eeea6SBoris Brezillon 	oobregion->length = sas - chip->ecc.bytes;
1265ef5eeea6SBoris Brezillon 
1266ef5eeea6SBoris Brezillon 	if (!section) {
1267ef5eeea6SBoris Brezillon 		oobregion->offset++;
1268ef5eeea6SBoris Brezillon 		oobregion->length--;
1269ef5eeea6SBoris Brezillon 	}
1270ef5eeea6SBoris Brezillon 
1271ef5eeea6SBoris Brezillon 	return 0;
1272ef5eeea6SBoris Brezillon }
1273ef5eeea6SBoris Brezillon 
1274ef5eeea6SBoris Brezillon static int brcmnand_bch_ooblayout_free_sp(struct mtd_info *mtd, int section,
1275ef5eeea6SBoris Brezillon 					  struct mtd_oob_region *oobregion)
1276ef5eeea6SBoris Brezillon {
1277ef5eeea6SBoris Brezillon 	struct nand_chip *chip = mtd_to_nand(mtd);
1278ef5eeea6SBoris Brezillon 	struct brcmnand_host *host = nand_get_controller_data(chip);
1279ef5eeea6SBoris Brezillon 	struct brcmnand_cfg *cfg = &host->hwcfg;
1280ef5eeea6SBoris Brezillon 	int sas = cfg->spare_area_size << cfg->sector_size_1k;
1281ef5eeea6SBoris Brezillon 
1282ef5eeea6SBoris Brezillon 	if (section > 1 || sas - chip->ecc.bytes < 6 ||
1283ef5eeea6SBoris Brezillon 	    (section && sas - chip->ecc.bytes == 6))
1284ef5eeea6SBoris Brezillon 		return -ERANGE;
1285ef5eeea6SBoris Brezillon 
1286ef5eeea6SBoris Brezillon 	if (!section) {
1287ef5eeea6SBoris Brezillon 		oobregion->offset = 0;
1288ef5eeea6SBoris Brezillon 		oobregion->length = 5;
1289ef5eeea6SBoris Brezillon 	} else {
1290ef5eeea6SBoris Brezillon 		oobregion->offset = 6;
1291ef5eeea6SBoris Brezillon 		oobregion->length = sas - chip->ecc.bytes - 6;
1292ef5eeea6SBoris Brezillon 	}
1293ef5eeea6SBoris Brezillon 
1294ef5eeea6SBoris Brezillon 	return 0;
1295ef5eeea6SBoris Brezillon }
1296ef5eeea6SBoris Brezillon 
1297ef5eeea6SBoris Brezillon static const struct mtd_ooblayout_ops brcmnand_bch_lp_ooblayout_ops = {
1298ef5eeea6SBoris Brezillon 	.ecc = brcmnand_bch_ooblayout_ecc,
1299ef5eeea6SBoris Brezillon 	.free = brcmnand_bch_ooblayout_free_lp,
1300ef5eeea6SBoris Brezillon };
1301ef5eeea6SBoris Brezillon 
1302ef5eeea6SBoris Brezillon static const struct mtd_ooblayout_ops brcmnand_bch_sp_ooblayout_ops = {
1303ef5eeea6SBoris Brezillon 	.ecc = brcmnand_bch_ooblayout_ecc,
1304ef5eeea6SBoris Brezillon 	.free = brcmnand_bch_ooblayout_free_sp,
1305ef5eeea6SBoris Brezillon };
1306ef5eeea6SBoris Brezillon 
1307ef5eeea6SBoris Brezillon static int brcmstb_choose_ecc_layout(struct brcmnand_host *host)
1308ef5eeea6SBoris Brezillon {
1309ef5eeea6SBoris Brezillon 	struct brcmnand_cfg *p = &host->hwcfg;
1310ef5eeea6SBoris Brezillon 	struct mtd_info *mtd = nand_to_mtd(&host->chip);
1311ef5eeea6SBoris Brezillon 	struct nand_ecc_ctrl *ecc = &host->chip.ecc;
1312ef5eeea6SBoris Brezillon 	unsigned int ecc_level = p->ecc_level;
1313ef5eeea6SBoris Brezillon 	int sas = p->spare_area_size << p->sector_size_1k;
1314ef5eeea6SBoris Brezillon 	int sectors = p->page_size / (512 << p->sector_size_1k);
1315ef5eeea6SBoris Brezillon 
1316ef5eeea6SBoris Brezillon 	if (p->sector_size_1k)
1317ef5eeea6SBoris Brezillon 		ecc_level <<= 1;
1318ef5eeea6SBoris Brezillon 
1319decba6d4SFlorian Fainelli 	if (is_hamming_ecc(host->ctrl, p)) {
1320ef5eeea6SBoris Brezillon 		ecc->bytes = 3 * sectors;
1321ef5eeea6SBoris Brezillon 		mtd_set_ooblayout(mtd, &brcmnand_hamming_ooblayout_ops);
1322ef5eeea6SBoris Brezillon 		return 0;
132327c5b17cSBrian Norris 	}
132427c5b17cSBrian Norris 
132527c5b17cSBrian Norris 	/*
132627c5b17cSBrian Norris 	 * CONTROLLER_VERSION:
132727c5b17cSBrian Norris 	 *   < v5.0: ECC_REQ = ceil(BCH_T * 13/8)
132827c5b17cSBrian Norris 	 *  >= v5.0: ECC_REQ = ceil(BCH_T * 14/8)
132927c5b17cSBrian Norris 	 * But we will just be conservative.
133027c5b17cSBrian Norris 	 */
1331ef5eeea6SBoris Brezillon 	ecc->bytes = DIV_ROUND_UP(ecc_level * 14, 8);
1332ef5eeea6SBoris Brezillon 	if (p->page_size == 512)
1333ef5eeea6SBoris Brezillon 		mtd_set_ooblayout(mtd, &brcmnand_bch_sp_ooblayout_ops);
1334ef5eeea6SBoris Brezillon 	else
1335ef5eeea6SBoris Brezillon 		mtd_set_ooblayout(mtd, &brcmnand_bch_lp_ooblayout_ops);
1336ef5eeea6SBoris Brezillon 
1337ef5eeea6SBoris Brezillon 	if (ecc->bytes >= sas) {
133827c5b17cSBrian Norris 		dev_err(&host->pdev->dev,
133927c5b17cSBrian Norris 			"error: ECC too large for OOB (ECC bytes %d, spare sector %d)\n",
1340ef5eeea6SBoris Brezillon 			ecc->bytes, sas);
1341ef5eeea6SBoris Brezillon 		return -EINVAL;
134227c5b17cSBrian Norris 	}
134327c5b17cSBrian Norris 
1344ef5eeea6SBoris Brezillon 	return 0;
134527c5b17cSBrian Norris }
134627c5b17cSBrian Norris 
134727c5b17cSBrian Norris static void brcmnand_wp(struct mtd_info *mtd, int wp)
134827c5b17cSBrian Norris {
13494bd4ebccSBoris BREZILLON 	struct nand_chip *chip = mtd_to_nand(mtd);
1350d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
135127c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
135227c5b17cSBrian Norris 
135327c5b17cSBrian Norris 	if ((ctrl->features & BRCMNAND_HAS_WP) && wp_on == 1) {
135427c5b17cSBrian Norris 		static int old_wp = -1;
13559d2ee0a6SKamal Dasu 		int ret;
135627c5b17cSBrian Norris 
135727c5b17cSBrian Norris 		if (old_wp != wp) {
135827c5b17cSBrian Norris 			dev_dbg(ctrl->dev, "WP %s\n", wp ? "on" : "off");
135927c5b17cSBrian Norris 			old_wp = wp;
136027c5b17cSBrian Norris 		}
13619d2ee0a6SKamal Dasu 
13629d2ee0a6SKamal Dasu 		/*
13639d2ee0a6SKamal Dasu 		 * make sure ctrl/flash ready before and after
13649d2ee0a6SKamal Dasu 		 * changing state of #WP pin
13659d2ee0a6SKamal Dasu 		 */
13669d2ee0a6SKamal Dasu 		ret = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY |
13679d2ee0a6SKamal Dasu 					       NAND_STATUS_READY,
13689d2ee0a6SKamal Dasu 					       NAND_CTRL_RDY |
13699d2ee0a6SKamal Dasu 					       NAND_STATUS_READY, 0);
13709d2ee0a6SKamal Dasu 		if (ret)
13719d2ee0a6SKamal Dasu 			return;
13729d2ee0a6SKamal Dasu 
137327c5b17cSBrian Norris 		brcmnand_set_wp(ctrl, wp);
137497d90da8SBoris Brezillon 		nand_status_op(chip, NULL);
13759d2ee0a6SKamal Dasu 		/* NAND_STATUS_WP 0x00 = protected, 0x80 = not protected */
13769d2ee0a6SKamal Dasu 		ret = bcmnand_ctrl_poll_status(ctrl,
13779d2ee0a6SKamal Dasu 					       NAND_CTRL_RDY |
13789d2ee0a6SKamal Dasu 					       NAND_STATUS_READY |
13799d2ee0a6SKamal Dasu 					       NAND_STATUS_WP,
13809d2ee0a6SKamal Dasu 					       NAND_CTRL_RDY |
13819d2ee0a6SKamal Dasu 					       NAND_STATUS_READY |
13829d2ee0a6SKamal Dasu 					       (wp ? 0 : NAND_STATUS_WP), 0);
13839d2ee0a6SKamal Dasu 
13849d2ee0a6SKamal Dasu 		if (ret)
13859d2ee0a6SKamal Dasu 			dev_err_ratelimited(&host->pdev->dev,
13869d2ee0a6SKamal Dasu 					    "nand #WP expected %s\n",
13879d2ee0a6SKamal Dasu 					    wp ? "on" : "off");
138827c5b17cSBrian Norris 	}
138927c5b17cSBrian Norris }
139027c5b17cSBrian Norris 
139127c5b17cSBrian Norris /* Helper functions for reading and writing OOB registers */
139227c5b17cSBrian Norris static inline u8 oob_reg_read(struct brcmnand_controller *ctrl, u32 offs)
139327c5b17cSBrian Norris {
139427c5b17cSBrian Norris 	u16 offset0, offset10, reg_offs;
139527c5b17cSBrian Norris 
139627c5b17cSBrian Norris 	offset0 = ctrl->reg_offsets[BRCMNAND_OOB_READ_BASE];
139727c5b17cSBrian Norris 	offset10 = ctrl->reg_offsets[BRCMNAND_OOB_READ_10_BASE];
139827c5b17cSBrian Norris 
139927c5b17cSBrian Norris 	if (offs >= ctrl->max_oob)
140027c5b17cSBrian Norris 		return 0x77;
140127c5b17cSBrian Norris 
140227c5b17cSBrian Norris 	if (offs >= 16 && offset10)
140327c5b17cSBrian Norris 		reg_offs = offset10 + ((offs - 0x10) & ~0x03);
140427c5b17cSBrian Norris 	else
140527c5b17cSBrian Norris 		reg_offs = offset0 + (offs & ~0x03);
140627c5b17cSBrian Norris 
140727c5b17cSBrian Norris 	return nand_readreg(ctrl, reg_offs) >> (24 - ((offs & 0x03) << 3));
140827c5b17cSBrian Norris }
140927c5b17cSBrian Norris 
141027c5b17cSBrian Norris static inline void oob_reg_write(struct brcmnand_controller *ctrl, u32 offs,
141127c5b17cSBrian Norris 				 u32 data)
141227c5b17cSBrian Norris {
141327c5b17cSBrian Norris 	u16 offset0, offset10, reg_offs;
141427c5b17cSBrian Norris 
141527c5b17cSBrian Norris 	offset0 = ctrl->reg_offsets[BRCMNAND_OOB_WRITE_BASE];
141627c5b17cSBrian Norris 	offset10 = ctrl->reg_offsets[BRCMNAND_OOB_WRITE_10_BASE];
141727c5b17cSBrian Norris 
141827c5b17cSBrian Norris 	if (offs >= ctrl->max_oob)
141927c5b17cSBrian Norris 		return;
142027c5b17cSBrian Norris 
142127c5b17cSBrian Norris 	if (offs >= 16 && offset10)
142227c5b17cSBrian Norris 		reg_offs = offset10 + ((offs - 0x10) & ~0x03);
142327c5b17cSBrian Norris 	else
142427c5b17cSBrian Norris 		reg_offs = offset0 + (offs & ~0x03);
142527c5b17cSBrian Norris 
142627c5b17cSBrian Norris 	nand_writereg(ctrl, reg_offs, data);
142727c5b17cSBrian Norris }
142827c5b17cSBrian Norris 
142927c5b17cSBrian Norris /*
143027c5b17cSBrian Norris  * read_oob_from_regs - read data from OOB registers
143127c5b17cSBrian Norris  * @ctrl: NAND controller
143227c5b17cSBrian Norris  * @i: sub-page sector index
143327c5b17cSBrian Norris  * @oob: buffer to read to
143427c5b17cSBrian Norris  * @sas: spare area sector size (i.e., OOB size per FLASH_CACHE)
143527c5b17cSBrian Norris  * @sector_1k: 1 for 1KiB sectors, 0 for 512B, other values are illegal
143627c5b17cSBrian Norris  */
143727c5b17cSBrian Norris static int read_oob_from_regs(struct brcmnand_controller *ctrl, int i, u8 *oob,
143827c5b17cSBrian Norris 			      int sas, int sector_1k)
143927c5b17cSBrian Norris {
144027c5b17cSBrian Norris 	int tbytes = sas << sector_1k;
144127c5b17cSBrian Norris 	int j;
144227c5b17cSBrian Norris 
144327c5b17cSBrian Norris 	/* Adjust OOB values for 1K sector size */
144427c5b17cSBrian Norris 	if (sector_1k && (i & 0x01))
144527c5b17cSBrian Norris 		tbytes = max(0, tbytes - (int)ctrl->max_oob);
144627c5b17cSBrian Norris 	tbytes = min_t(int, tbytes, ctrl->max_oob);
144727c5b17cSBrian Norris 
144827c5b17cSBrian Norris 	for (j = 0; j < tbytes; j++)
144927c5b17cSBrian Norris 		oob[j] = oob_reg_read(ctrl, j);
145027c5b17cSBrian Norris 	return tbytes;
145127c5b17cSBrian Norris }
145227c5b17cSBrian Norris 
145327c5b17cSBrian Norris /*
145427c5b17cSBrian Norris  * write_oob_to_regs - write data to OOB registers
145527c5b17cSBrian Norris  * @i: sub-page sector index
145627c5b17cSBrian Norris  * @oob: buffer to write from
145727c5b17cSBrian Norris  * @sas: spare area sector size (i.e., OOB size per FLASH_CACHE)
145827c5b17cSBrian Norris  * @sector_1k: 1 for 1KiB sectors, 0 for 512B, other values are illegal
145927c5b17cSBrian Norris  */
146027c5b17cSBrian Norris static int write_oob_to_regs(struct brcmnand_controller *ctrl, int i,
146127c5b17cSBrian Norris 			     const u8 *oob, int sas, int sector_1k)
146227c5b17cSBrian Norris {
146327c5b17cSBrian Norris 	int tbytes = sas << sector_1k;
146427c5b17cSBrian Norris 	int j;
146527c5b17cSBrian Norris 
146627c5b17cSBrian Norris 	/* Adjust OOB values for 1K sector size */
146727c5b17cSBrian Norris 	if (sector_1k && (i & 0x01))
146827c5b17cSBrian Norris 		tbytes = max(0, tbytes - (int)ctrl->max_oob);
146927c5b17cSBrian Norris 	tbytes = min_t(int, tbytes, ctrl->max_oob);
147027c5b17cSBrian Norris 
147127c5b17cSBrian Norris 	for (j = 0; j < tbytes; j += 4)
147227c5b17cSBrian Norris 		oob_reg_write(ctrl, j,
147327c5b17cSBrian Norris 				(oob[j + 0] << 24) |
147427c5b17cSBrian Norris 				(oob[j + 1] << 16) |
147527c5b17cSBrian Norris 				(oob[j + 2] <<  8) |
147627c5b17cSBrian Norris 				(oob[j + 3] <<  0));
147727c5b17cSBrian Norris 	return tbytes;
147827c5b17cSBrian Norris }
147927c5b17cSBrian Norris 
1480a5d53ad2SKamal Dasu static void brcmnand_edu_init(struct brcmnand_controller *ctrl)
1481a5d53ad2SKamal Dasu {
1482a5d53ad2SKamal Dasu 	/* initialize edu */
1483a5d53ad2SKamal Dasu 	edu_writel(ctrl, EDU_ERR_STATUS, 0);
1484a5d53ad2SKamal Dasu 	edu_readl(ctrl, EDU_ERR_STATUS);
1485a5d53ad2SKamal Dasu 	edu_writel(ctrl, EDU_DONE, 0);
1486a5d53ad2SKamal Dasu 	edu_writel(ctrl, EDU_DONE, 0);
1487a5d53ad2SKamal Dasu 	edu_writel(ctrl, EDU_DONE, 0);
1488a5d53ad2SKamal Dasu 	edu_writel(ctrl, EDU_DONE, 0);
1489a5d53ad2SKamal Dasu 	edu_readl(ctrl, EDU_DONE);
1490a5d53ad2SKamal Dasu }
1491a5d53ad2SKamal Dasu 
1492a5d53ad2SKamal Dasu /* edu irq */
1493a5d53ad2SKamal Dasu static irqreturn_t brcmnand_edu_irq(int irq, void *data)
1494a5d53ad2SKamal Dasu {
1495a5d53ad2SKamal Dasu 	struct brcmnand_controller *ctrl = data;
1496a5d53ad2SKamal Dasu 
1497a5d53ad2SKamal Dasu 	if (ctrl->edu_count) {
1498a5d53ad2SKamal Dasu 		ctrl->edu_count--;
1499a5d53ad2SKamal Dasu 		while (!(edu_readl(ctrl, EDU_DONE) & EDU_DONE_MASK))
1500a5d53ad2SKamal Dasu 			udelay(1);
1501a5d53ad2SKamal Dasu 		edu_writel(ctrl, EDU_DONE, 0);
1502a5d53ad2SKamal Dasu 		edu_readl(ctrl, EDU_DONE);
1503a5d53ad2SKamal Dasu 	}
1504a5d53ad2SKamal Dasu 
1505a5d53ad2SKamal Dasu 	if (ctrl->edu_count) {
1506a5d53ad2SKamal Dasu 		ctrl->edu_dram_addr += FC_BYTES;
1507a5d53ad2SKamal Dasu 		ctrl->edu_ext_addr += FC_BYTES;
1508a5d53ad2SKamal Dasu 
1509a5d53ad2SKamal Dasu 		edu_writel(ctrl, EDU_DRAM_ADDR, (u32)ctrl->edu_dram_addr);
1510a5d53ad2SKamal Dasu 		edu_readl(ctrl, EDU_DRAM_ADDR);
1511a5d53ad2SKamal Dasu 		edu_writel(ctrl, EDU_EXT_ADDR, ctrl->edu_ext_addr);
1512a5d53ad2SKamal Dasu 		edu_readl(ctrl, EDU_EXT_ADDR);
1513a5d53ad2SKamal Dasu 
1514a0719126SKamal Dasu 		if (ctrl->oob) {
1515a0719126SKamal Dasu 			if (ctrl->edu_cmd == EDU_CMD_READ) {
1516a0719126SKamal Dasu 				ctrl->oob += read_oob_from_regs(ctrl,
1517a0719126SKamal Dasu 							ctrl->edu_count + 1,
1518a0719126SKamal Dasu 							ctrl->oob, ctrl->sas,
1519a0719126SKamal Dasu 							ctrl->sector_size_1k);
1520a0719126SKamal Dasu 			} else {
1521a0719126SKamal Dasu 				brcmnand_write_reg(ctrl, BRCMNAND_CMD_ADDRESS,
1522a0719126SKamal Dasu 						   ctrl->edu_ext_addr);
1523a0719126SKamal Dasu 				brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS);
1524a0719126SKamal Dasu 				ctrl->oob += write_oob_to_regs(ctrl,
1525a0719126SKamal Dasu 							       ctrl->edu_count,
1526a0719126SKamal Dasu 							       ctrl->oob, ctrl->sas,
1527a0719126SKamal Dasu 							       ctrl->sector_size_1k);
1528a0719126SKamal Dasu 			}
1529a0719126SKamal Dasu 		}
1530a0719126SKamal Dasu 
1531a5d53ad2SKamal Dasu 		mb(); /* flush previous writes */
1532a5d53ad2SKamal Dasu 		edu_writel(ctrl, EDU_CMD, ctrl->edu_cmd);
1533a5d53ad2SKamal Dasu 		edu_readl(ctrl, EDU_CMD);
1534a5d53ad2SKamal Dasu 
1535a5d53ad2SKamal Dasu 		return IRQ_HANDLED;
1536a5d53ad2SKamal Dasu 	}
1537a5d53ad2SKamal Dasu 
1538a5d53ad2SKamal Dasu 	complete(&ctrl->edu_done);
1539a5d53ad2SKamal Dasu 
1540a5d53ad2SKamal Dasu 	return IRQ_HANDLED;
1541a5d53ad2SKamal Dasu }
1542a5d53ad2SKamal Dasu 
154327c5b17cSBrian Norris static irqreturn_t brcmnand_ctlrdy_irq(int irq, void *data)
154427c5b17cSBrian Norris {
154527c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = data;
154627c5b17cSBrian Norris 
154727c5b17cSBrian Norris 	/* Discard all NAND_CTLRDY interrupts during DMA */
154827c5b17cSBrian Norris 	if (ctrl->dma_pending)
154927c5b17cSBrian Norris 		return IRQ_HANDLED;
155027c5b17cSBrian Norris 
1551a5d53ad2SKamal Dasu 	/* check if you need to piggy back on the ctrlrdy irq */
1552a5d53ad2SKamal Dasu 	if (ctrl->edu_pending) {
1553a5d53ad2SKamal Dasu 		if (irq == ctrl->irq && ((int)ctrl->edu_irq >= 0))
1554a5d53ad2SKamal Dasu 	/* Discard interrupts while using dedicated edu irq */
1555a5d53ad2SKamal Dasu 			return IRQ_HANDLED;
1556a5d53ad2SKamal Dasu 
1557a5d53ad2SKamal Dasu 	/* no registered edu irq, call handler */
1558a5d53ad2SKamal Dasu 		return brcmnand_edu_irq(irq, data);
1559a5d53ad2SKamal Dasu 	}
1560a5d53ad2SKamal Dasu 
156127c5b17cSBrian Norris 	complete(&ctrl->done);
156227c5b17cSBrian Norris 	return IRQ_HANDLED;
156327c5b17cSBrian Norris }
156427c5b17cSBrian Norris 
1565c26211d3SBrian Norris /* Handle SoC-specific interrupt hardware */
1566c26211d3SBrian Norris static irqreturn_t brcmnand_irq(int irq, void *data)
1567c26211d3SBrian Norris {
1568c26211d3SBrian Norris 	struct brcmnand_controller *ctrl = data;
1569c26211d3SBrian Norris 
1570c26211d3SBrian Norris 	if (ctrl->soc->ctlrdy_ack(ctrl->soc))
1571c26211d3SBrian Norris 		return brcmnand_ctlrdy_irq(irq, data);
1572c26211d3SBrian Norris 
1573c26211d3SBrian Norris 	return IRQ_NONE;
1574c26211d3SBrian Norris }
1575c26211d3SBrian Norris 
157627c5b17cSBrian Norris static irqreturn_t brcmnand_dma_irq(int irq, void *data)
157727c5b17cSBrian Norris {
157827c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = data;
157927c5b17cSBrian Norris 
158027c5b17cSBrian Norris 	complete(&ctrl->dma_done);
158127c5b17cSBrian Norris 
158227c5b17cSBrian Norris 	return IRQ_HANDLED;
158327c5b17cSBrian Norris }
158427c5b17cSBrian Norris 
158527c5b17cSBrian Norris static void brcmnand_send_cmd(struct brcmnand_host *host, int cmd)
158627c5b17cSBrian Norris {
158727c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
15889d2ee0a6SKamal Dasu 	int ret;
15893c7c1e45SKamal Dasu 	u64 cmd_addr;
159027c5b17cSBrian Norris 
15913c7c1e45SKamal Dasu 	cmd_addr = brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS);
15923c7c1e45SKamal Dasu 
15933c7c1e45SKamal Dasu 	dev_dbg(ctrl->dev, "send native cmd %d addr 0x%llx\n", cmd, cmd_addr);
15943c7c1e45SKamal Dasu 
159527c5b17cSBrian Norris 	BUG_ON(ctrl->cmd_pending != 0);
159627c5b17cSBrian Norris 	ctrl->cmd_pending = cmd;
159727c5b17cSBrian Norris 
15989d2ee0a6SKamal Dasu 	ret = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY, NAND_CTRL_RDY, 0);
15999d2ee0a6SKamal Dasu 	WARN_ON(ret);
160027c5b17cSBrian Norris 
160127c5b17cSBrian Norris 	mb(); /* flush previous writes */
160227c5b17cSBrian Norris 	brcmnand_write_reg(ctrl, BRCMNAND_CMD_START,
160327c5b17cSBrian Norris 			   cmd << brcmnand_cmd_shift(ctrl));
160427c5b17cSBrian Norris }
160527c5b17cSBrian Norris 
160627c5b17cSBrian Norris /***********************************************************************
160727c5b17cSBrian Norris  * NAND MTD API: read/program/erase
160827c5b17cSBrian Norris  ***********************************************************************/
160927c5b17cSBrian Norris 
16100f808c16SBoris Brezillon static void brcmnand_cmd_ctrl(struct nand_chip *chip, int dat,
161127c5b17cSBrian Norris 			      unsigned int ctrl)
161227c5b17cSBrian Norris {
161327c5b17cSBrian Norris 	/* intentionally left blank */
161427c5b17cSBrian Norris }
161527c5b17cSBrian Norris 
1616c1ac2dc3SKamal Dasu static bool brcmstb_nand_wait_for_completion(struct nand_chip *chip)
1617c1ac2dc3SKamal Dasu {
1618c1ac2dc3SKamal Dasu 	struct brcmnand_host *host = nand_get_controller_data(chip);
1619c1ac2dc3SKamal Dasu 	struct brcmnand_controller *ctrl = host->ctrl;
1620c1ac2dc3SKamal Dasu 	struct mtd_info *mtd = nand_to_mtd(chip);
1621c1ac2dc3SKamal Dasu 	bool err = false;
1622c1ac2dc3SKamal Dasu 	int sts;
1623c1ac2dc3SKamal Dasu 
1624f5619f37SFlorian Fainelli 	if (mtd->oops_panic_write || ctrl->irq < 0) {
1625c1ac2dc3SKamal Dasu 		/* switch to interrupt polling and PIO mode */
1626c1ac2dc3SKamal Dasu 		disable_ctrl_irqs(ctrl);
1627c1ac2dc3SKamal Dasu 		sts = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY,
1628c1ac2dc3SKamal Dasu 					       NAND_CTRL_RDY, 0);
1629c1ac2dc3SKamal Dasu 		err = (sts < 0) ? true : false;
1630c1ac2dc3SKamal Dasu 	} else {
1631c1ac2dc3SKamal Dasu 		unsigned long timeo = msecs_to_jiffies(
1632c1ac2dc3SKamal Dasu 						NAND_POLL_STATUS_TIMEOUT_MS);
1633c1ac2dc3SKamal Dasu 		/* wait for completion interrupt */
1634c1ac2dc3SKamal Dasu 		sts = wait_for_completion_timeout(&ctrl->done, timeo);
1635c1ac2dc3SKamal Dasu 		err = (sts <= 0) ? true : false;
1636c1ac2dc3SKamal Dasu 	}
1637c1ac2dc3SKamal Dasu 
1638c1ac2dc3SKamal Dasu 	return err;
1639c1ac2dc3SKamal Dasu }
1640c1ac2dc3SKamal Dasu 
1641f1d46942SBoris Brezillon static int brcmnand_waitfunc(struct nand_chip *chip)
164227c5b17cSBrian Norris {
1643d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
164427c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
1645c1ac2dc3SKamal Dasu 	bool err = false;
164627c5b17cSBrian Norris 
164727c5b17cSBrian Norris 	dev_dbg(ctrl->dev, "wait on native cmd %d\n", ctrl->cmd_pending);
1648c1ac2dc3SKamal Dasu 	if (ctrl->cmd_pending)
1649c1ac2dc3SKamal Dasu 		err = brcmstb_nand_wait_for_completion(chip);
1650c1ac2dc3SKamal Dasu 
1651c1ac2dc3SKamal Dasu 	if (err) {
165227c5b17cSBrian Norris 		u32 cmd = brcmnand_read_reg(ctrl, BRCMNAND_CMD_START)
165327c5b17cSBrian Norris 					>> brcmnand_cmd_shift(ctrl);
165427c5b17cSBrian Norris 
165527c5b17cSBrian Norris 		dev_err_ratelimited(ctrl->dev,
165627c5b17cSBrian Norris 			"timeout waiting for command %#02x\n", cmd);
165727c5b17cSBrian Norris 		dev_err_ratelimited(ctrl->dev, "intfc status %08x\n",
165827c5b17cSBrian Norris 			brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS));
165927c5b17cSBrian Norris 	}
166027c5b17cSBrian Norris 	ctrl->cmd_pending = 0;
166127c5b17cSBrian Norris 	return brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS) &
166227c5b17cSBrian Norris 				 INTFC_FLASH_STATUS;
166327c5b17cSBrian Norris }
166427c5b17cSBrian Norris 
166527c5b17cSBrian Norris enum {
166627c5b17cSBrian Norris 	LLOP_RE				= BIT(16),
166727c5b17cSBrian Norris 	LLOP_WE				= BIT(17),
166827c5b17cSBrian Norris 	LLOP_ALE			= BIT(18),
166927c5b17cSBrian Norris 	LLOP_CLE			= BIT(19),
167027c5b17cSBrian Norris 	LLOP_RETURN_IDLE		= BIT(31),
167127c5b17cSBrian Norris 
167227c5b17cSBrian Norris 	LLOP_DATA_MASK			= GENMASK(15, 0),
167327c5b17cSBrian Norris };
167427c5b17cSBrian Norris 
167527c5b17cSBrian Norris static int brcmnand_low_level_op(struct brcmnand_host *host,
167627c5b17cSBrian Norris 				 enum brcmnand_llop_type type, u32 data,
167727c5b17cSBrian Norris 				 bool last_op)
167827c5b17cSBrian Norris {
167927c5b17cSBrian Norris 	struct nand_chip *chip = &host->chip;
168027c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
168127c5b17cSBrian Norris 	u32 tmp;
168227c5b17cSBrian Norris 
168327c5b17cSBrian Norris 	tmp = data & LLOP_DATA_MASK;
168427c5b17cSBrian Norris 	switch (type) {
168527c5b17cSBrian Norris 	case LL_OP_CMD:
168627c5b17cSBrian Norris 		tmp |= LLOP_WE | LLOP_CLE;
168727c5b17cSBrian Norris 		break;
168827c5b17cSBrian Norris 	case LL_OP_ADDR:
168927c5b17cSBrian Norris 		/* WE | ALE */
169027c5b17cSBrian Norris 		tmp |= LLOP_WE | LLOP_ALE;
169127c5b17cSBrian Norris 		break;
169227c5b17cSBrian Norris 	case LL_OP_WR:
169327c5b17cSBrian Norris 		/* WE */
169427c5b17cSBrian Norris 		tmp |= LLOP_WE;
169527c5b17cSBrian Norris 		break;
169627c5b17cSBrian Norris 	case LL_OP_RD:
169727c5b17cSBrian Norris 		/* RE */
169827c5b17cSBrian Norris 		tmp |= LLOP_RE;
169927c5b17cSBrian Norris 		break;
170027c5b17cSBrian Norris 	}
170127c5b17cSBrian Norris 	if (last_op)
170227c5b17cSBrian Norris 		/* RETURN_IDLE */
170327c5b17cSBrian Norris 		tmp |= LLOP_RETURN_IDLE;
170427c5b17cSBrian Norris 
170527c5b17cSBrian Norris 	dev_dbg(ctrl->dev, "ll_op cmd %#x\n", tmp);
170627c5b17cSBrian Norris 
170727c5b17cSBrian Norris 	brcmnand_write_reg(ctrl, BRCMNAND_LL_OP, tmp);
170827c5b17cSBrian Norris 	(void)brcmnand_read_reg(ctrl, BRCMNAND_LL_OP);
170927c5b17cSBrian Norris 
171027c5b17cSBrian Norris 	brcmnand_send_cmd(host, CMD_LOW_LEVEL_OP);
1711f1d46942SBoris Brezillon 	return brcmnand_waitfunc(chip);
171227c5b17cSBrian Norris }
171327c5b17cSBrian Norris 
17145295cf2eSBoris Brezillon static void brcmnand_cmdfunc(struct nand_chip *chip, unsigned command,
171527c5b17cSBrian Norris 			     int column, int page_addr)
171627c5b17cSBrian Norris {
17175295cf2eSBoris Brezillon 	struct mtd_info *mtd = nand_to_mtd(chip);
1718d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
171927c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
172027c5b17cSBrian Norris 	u64 addr = (u64)page_addr << chip->page_shift;
172127c5b17cSBrian Norris 	int native_cmd = 0;
172227c5b17cSBrian Norris 
172327c5b17cSBrian Norris 	if (command == NAND_CMD_READID || command == NAND_CMD_PARAM ||
172427c5b17cSBrian Norris 			command == NAND_CMD_RNDOUT)
172527c5b17cSBrian Norris 		addr = (u64)column;
172627c5b17cSBrian Norris 	/* Avoid propagating a negative, don't-care address */
172727c5b17cSBrian Norris 	else if (page_addr < 0)
172827c5b17cSBrian Norris 		addr = 0;
172927c5b17cSBrian Norris 
173027c5b17cSBrian Norris 	dev_dbg(ctrl->dev, "cmd 0x%x addr 0x%llx\n", command,
173127c5b17cSBrian Norris 		(unsigned long long)addr);
173227c5b17cSBrian Norris 
173327c5b17cSBrian Norris 	host->last_cmd = command;
173427c5b17cSBrian Norris 	host->last_byte = 0;
173527c5b17cSBrian Norris 	host->last_addr = addr;
173627c5b17cSBrian Norris 
173727c5b17cSBrian Norris 	switch (command) {
173827c5b17cSBrian Norris 	case NAND_CMD_RESET:
173927c5b17cSBrian Norris 		native_cmd = CMD_FLASH_RESET;
174027c5b17cSBrian Norris 		break;
174127c5b17cSBrian Norris 	case NAND_CMD_STATUS:
174227c5b17cSBrian Norris 		native_cmd = CMD_STATUS_READ;
174327c5b17cSBrian Norris 		break;
174427c5b17cSBrian Norris 	case NAND_CMD_READID:
174527c5b17cSBrian Norris 		native_cmd = CMD_DEVICE_ID_READ;
174627c5b17cSBrian Norris 		break;
174727c5b17cSBrian Norris 	case NAND_CMD_READOOB:
174827c5b17cSBrian Norris 		native_cmd = CMD_SPARE_AREA_READ;
174927c5b17cSBrian Norris 		break;
175027c5b17cSBrian Norris 	case NAND_CMD_ERASE1:
175127c5b17cSBrian Norris 		native_cmd = CMD_BLOCK_ERASE;
175227c5b17cSBrian Norris 		brcmnand_wp(mtd, 0);
175327c5b17cSBrian Norris 		break;
175427c5b17cSBrian Norris 	case NAND_CMD_PARAM:
175527c5b17cSBrian Norris 		native_cmd = CMD_PARAMETER_READ;
175627c5b17cSBrian Norris 		break;
175727c5b17cSBrian Norris 	case NAND_CMD_SET_FEATURES:
175827c5b17cSBrian Norris 	case NAND_CMD_GET_FEATURES:
175927c5b17cSBrian Norris 		brcmnand_low_level_op(host, LL_OP_CMD, command, false);
176027c5b17cSBrian Norris 		brcmnand_low_level_op(host, LL_OP_ADDR, column, false);
176127c5b17cSBrian Norris 		break;
176227c5b17cSBrian Norris 	case NAND_CMD_RNDOUT:
176327c5b17cSBrian Norris 		native_cmd = CMD_PARAMETER_CHANGE_COL;
176427c5b17cSBrian Norris 		addr &= ~((u64)(FC_BYTES - 1));
176527c5b17cSBrian Norris 		/*
176627c5b17cSBrian Norris 		 * HW quirk: PARAMETER_CHANGE_COL requires SECTOR_SIZE_1K=0
176727c5b17cSBrian Norris 		 * NB: hwcfg.sector_size_1k may not be initialized yet
176827c5b17cSBrian Norris 		 */
176927c5b17cSBrian Norris 		if (brcmnand_get_sector_size_1k(host)) {
177027c5b17cSBrian Norris 			host->hwcfg.sector_size_1k =
177127c5b17cSBrian Norris 				brcmnand_get_sector_size_1k(host);
177227c5b17cSBrian Norris 			brcmnand_set_sector_size_1k(host, 0);
177327c5b17cSBrian Norris 		}
177427c5b17cSBrian Norris 		break;
177527c5b17cSBrian Norris 	}
177627c5b17cSBrian Norris 
177727c5b17cSBrian Norris 	if (!native_cmd)
177827c5b17cSBrian Norris 		return;
177927c5b17cSBrian Norris 
17803c7c1e45SKamal Dasu 	brcmnand_set_cmd_addr(mtd, addr);
178127c5b17cSBrian Norris 	brcmnand_send_cmd(host, native_cmd);
1782f1d46942SBoris Brezillon 	brcmnand_waitfunc(chip);
178327c5b17cSBrian Norris 
178427c5b17cSBrian Norris 	if (native_cmd == CMD_PARAMETER_READ ||
178527c5b17cSBrian Norris 			native_cmd == CMD_PARAMETER_CHANGE_COL) {
1786d618baf9SBrian Norris 		/* Copy flash cache word-wise */
1787d618baf9SBrian Norris 		u32 *flash_cache = (u32 *)ctrl->flash_cache;
178827c5b17cSBrian Norris 		int i;
1789c26211d3SBrian Norris 
1790eab7fdc7SRay Jui 		brcmnand_soc_data_bus_prepare(ctrl->soc, true);
1791c26211d3SBrian Norris 
179227c5b17cSBrian Norris 		/*
179327c5b17cSBrian Norris 		 * Must cache the FLASH_CACHE now, since changes in
179427c5b17cSBrian Norris 		 * SECTOR_SIZE_1K may invalidate it
179527c5b17cSBrian Norris 		 */
179627c5b17cSBrian Norris 		for (i = 0; i < FC_WORDS; i++)
1797d618baf9SBrian Norris 			/*
1798d618baf9SBrian Norris 			 * Flash cache is big endian for parameter pages, at
1799d618baf9SBrian Norris 			 * least on STB SoCs
1800d618baf9SBrian Norris 			 */
1801d618baf9SBrian Norris 			flash_cache[i] = be32_to_cpu(brcmnand_read_fc(ctrl, i));
1802c26211d3SBrian Norris 
1803eab7fdc7SRay Jui 		brcmnand_soc_data_bus_unprepare(ctrl->soc, true);
1804c26211d3SBrian Norris 
180527c5b17cSBrian Norris 		/* Cleanup from HW quirk: restore SECTOR_SIZE_1K */
180627c5b17cSBrian Norris 		if (host->hwcfg.sector_size_1k)
180727c5b17cSBrian Norris 			brcmnand_set_sector_size_1k(host,
180827c5b17cSBrian Norris 						    host->hwcfg.sector_size_1k);
180927c5b17cSBrian Norris 	}
181027c5b17cSBrian Norris 
181127c5b17cSBrian Norris 	/* Re-enable protection is necessary only after erase */
181227c5b17cSBrian Norris 	if (command == NAND_CMD_ERASE1)
181327c5b17cSBrian Norris 		brcmnand_wp(mtd, 1);
181427c5b17cSBrian Norris }
181527c5b17cSBrian Norris 
18167e534323SBoris Brezillon static uint8_t brcmnand_read_byte(struct nand_chip *chip)
181727c5b17cSBrian Norris {
1818d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
181927c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
182027c5b17cSBrian Norris 	uint8_t ret = 0;
182127c5b17cSBrian Norris 	int addr, offs;
182227c5b17cSBrian Norris 
182327c5b17cSBrian Norris 	switch (host->last_cmd) {
182427c5b17cSBrian Norris 	case NAND_CMD_READID:
182527c5b17cSBrian Norris 		if (host->last_byte < 4)
182627c5b17cSBrian Norris 			ret = brcmnand_read_reg(ctrl, BRCMNAND_ID) >>
182727c5b17cSBrian Norris 				(24 - (host->last_byte << 3));
182827c5b17cSBrian Norris 		else if (host->last_byte < 8)
182927c5b17cSBrian Norris 			ret = brcmnand_read_reg(ctrl, BRCMNAND_ID_EXT) >>
183027c5b17cSBrian Norris 				(56 - (host->last_byte << 3));
183127c5b17cSBrian Norris 		break;
183227c5b17cSBrian Norris 
183327c5b17cSBrian Norris 	case NAND_CMD_READOOB:
183427c5b17cSBrian Norris 		ret = oob_reg_read(ctrl, host->last_byte);
183527c5b17cSBrian Norris 		break;
183627c5b17cSBrian Norris 
183727c5b17cSBrian Norris 	case NAND_CMD_STATUS:
183827c5b17cSBrian Norris 		ret = brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS) &
183927c5b17cSBrian Norris 					INTFC_FLASH_STATUS;
184027c5b17cSBrian Norris 		if (wp_on) /* hide WP status */
184127c5b17cSBrian Norris 			ret |= NAND_STATUS_WP;
184227c5b17cSBrian Norris 		break;
184327c5b17cSBrian Norris 
184427c5b17cSBrian Norris 	case NAND_CMD_PARAM:
184527c5b17cSBrian Norris 	case NAND_CMD_RNDOUT:
184627c5b17cSBrian Norris 		addr = host->last_addr + host->last_byte;
184727c5b17cSBrian Norris 		offs = addr & (FC_BYTES - 1);
184827c5b17cSBrian Norris 
184927c5b17cSBrian Norris 		/* At FC_BYTES boundary, switch to next column */
185027c5b17cSBrian Norris 		if (host->last_byte > 0 && offs == 0)
185197d90da8SBoris Brezillon 			nand_change_read_column_op(chip, addr, NULL, 0, false);
185227c5b17cSBrian Norris 
1853d618baf9SBrian Norris 		ret = ctrl->flash_cache[offs];
185427c5b17cSBrian Norris 		break;
185527c5b17cSBrian Norris 	case NAND_CMD_GET_FEATURES:
185627c5b17cSBrian Norris 		if (host->last_byte >= ONFI_SUBFEATURE_PARAM_LEN) {
185727c5b17cSBrian Norris 			ret = 0;
185827c5b17cSBrian Norris 		} else {
185927c5b17cSBrian Norris 			bool last = host->last_byte ==
186027c5b17cSBrian Norris 				ONFI_SUBFEATURE_PARAM_LEN - 1;
186127c5b17cSBrian Norris 			brcmnand_low_level_op(host, LL_OP_RD, 0, last);
186227c5b17cSBrian Norris 			ret = brcmnand_read_reg(ctrl, BRCMNAND_LL_RDATA) & 0xff;
186327c5b17cSBrian Norris 		}
186427c5b17cSBrian Norris 	}
186527c5b17cSBrian Norris 
186627c5b17cSBrian Norris 	dev_dbg(ctrl->dev, "read byte = 0x%02x\n", ret);
186727c5b17cSBrian Norris 	host->last_byte++;
186827c5b17cSBrian Norris 
186927c5b17cSBrian Norris 	return ret;
187027c5b17cSBrian Norris }
187127c5b17cSBrian Norris 
18727e534323SBoris Brezillon static void brcmnand_read_buf(struct nand_chip *chip, uint8_t *buf, int len)
187327c5b17cSBrian Norris {
187427c5b17cSBrian Norris 	int i;
187527c5b17cSBrian Norris 
187627c5b17cSBrian Norris 	for (i = 0; i < len; i++, buf++)
18777e534323SBoris Brezillon 		*buf = brcmnand_read_byte(chip);
187827c5b17cSBrian Norris }
187927c5b17cSBrian Norris 
1880c0739d85SBoris Brezillon static void brcmnand_write_buf(struct nand_chip *chip, const uint8_t *buf,
188127c5b17cSBrian Norris 			       int len)
188227c5b17cSBrian Norris {
188327c5b17cSBrian Norris 	int i;
1884d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
188527c5b17cSBrian Norris 
188627c5b17cSBrian Norris 	switch (host->last_cmd) {
188727c5b17cSBrian Norris 	case NAND_CMD_SET_FEATURES:
188827c5b17cSBrian Norris 		for (i = 0; i < len; i++)
188927c5b17cSBrian Norris 			brcmnand_low_level_op(host, LL_OP_WR, buf[i],
189027c5b17cSBrian Norris 						  (i + 1) == len);
189127c5b17cSBrian Norris 		break;
189227c5b17cSBrian Norris 	default:
189327c5b17cSBrian Norris 		BUG();
189427c5b17cSBrian Norris 		break;
189527c5b17cSBrian Norris 	}
189627c5b17cSBrian Norris }
189727c5b17cSBrian Norris 
1898fa985e22SLee Jones /*
1899a5d53ad2SKamal Dasu  *  Kick EDU engine
1900a5d53ad2SKamal Dasu  */
1901a5d53ad2SKamal Dasu static int brcmnand_edu_trans(struct brcmnand_host *host, u64 addr, u32 *buf,
1902a0719126SKamal Dasu 			      u8 *oob, u32 len, u8 cmd)
1903a5d53ad2SKamal Dasu {
1904a5d53ad2SKamal Dasu 	struct brcmnand_controller *ctrl = host->ctrl;
1905a0719126SKamal Dasu 	struct brcmnand_cfg *cfg = &host->hwcfg;
1906a5d53ad2SKamal Dasu 	unsigned long timeo = msecs_to_jiffies(200);
1907a5d53ad2SKamal Dasu 	int ret = 0;
1908a5d53ad2SKamal Dasu 	int dir = (cmd == CMD_PAGE_READ ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
1909a5d53ad2SKamal Dasu 	u8 edu_cmd = (cmd == CMD_PAGE_READ ? EDU_CMD_READ : EDU_CMD_WRITE);
1910a5d53ad2SKamal Dasu 	unsigned int trans = len >> FC_SHIFT;
1911a5d53ad2SKamal Dasu 	dma_addr_t pa;
1912a5d53ad2SKamal Dasu 
1913a0719126SKamal Dasu 	dev_dbg(ctrl->dev, "EDU %s %p:%p\n", ((edu_cmd == EDU_CMD_READ) ?
1914a0719126SKamal Dasu 					      "read" : "write"), buf, oob);
1915a0719126SKamal Dasu 
1916a5d53ad2SKamal Dasu 	pa = dma_map_single(ctrl->dev, buf, len, dir);
1917a5d53ad2SKamal Dasu 	if (dma_mapping_error(ctrl->dev, pa)) {
1918a5d53ad2SKamal Dasu 		dev_err(ctrl->dev, "unable to map buffer for EDU DMA\n");
1919a5d53ad2SKamal Dasu 		return -ENOMEM;
1920a5d53ad2SKamal Dasu 	}
1921a5d53ad2SKamal Dasu 
1922a5d53ad2SKamal Dasu 	ctrl->edu_pending = true;
1923a5d53ad2SKamal Dasu 	ctrl->edu_dram_addr = pa;
1924a5d53ad2SKamal Dasu 	ctrl->edu_ext_addr = addr;
1925a5d53ad2SKamal Dasu 	ctrl->edu_cmd = edu_cmd;
1926a5d53ad2SKamal Dasu 	ctrl->edu_count = trans;
1927a0719126SKamal Dasu 	ctrl->sas = cfg->spare_area_size;
1928a0719126SKamal Dasu 	ctrl->oob = oob;
1929a5d53ad2SKamal Dasu 
1930a5d53ad2SKamal Dasu 	edu_writel(ctrl, EDU_DRAM_ADDR, (u32)ctrl->edu_dram_addr);
1931a5d53ad2SKamal Dasu 	edu_readl(ctrl,  EDU_DRAM_ADDR);
1932a5d53ad2SKamal Dasu 	edu_writel(ctrl, EDU_EXT_ADDR, ctrl->edu_ext_addr);
1933a5d53ad2SKamal Dasu 	edu_readl(ctrl, EDU_EXT_ADDR);
1934a5d53ad2SKamal Dasu 	edu_writel(ctrl, EDU_LENGTH, FC_BYTES);
1935a5d53ad2SKamal Dasu 	edu_readl(ctrl, EDU_LENGTH);
1936a5d53ad2SKamal Dasu 
1937a0719126SKamal Dasu 	if (ctrl->oob && (ctrl->edu_cmd == EDU_CMD_WRITE)) {
1938a0719126SKamal Dasu 		brcmnand_write_reg(ctrl, BRCMNAND_CMD_ADDRESS,
1939a0719126SKamal Dasu 				   ctrl->edu_ext_addr);
1940a0719126SKamal Dasu 		brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS);
1941a0719126SKamal Dasu 		ctrl->oob += write_oob_to_regs(ctrl,
1942a0719126SKamal Dasu 					       1,
1943a0719126SKamal Dasu 					       ctrl->oob, ctrl->sas,
1944a0719126SKamal Dasu 					       ctrl->sector_size_1k);
1945a0719126SKamal Dasu 	}
1946a0719126SKamal Dasu 
1947a5d53ad2SKamal Dasu 	/* Start edu engine */
1948a5d53ad2SKamal Dasu 	mb(); /* flush previous writes */
1949a5d53ad2SKamal Dasu 	edu_writel(ctrl, EDU_CMD, ctrl->edu_cmd);
1950a5d53ad2SKamal Dasu 	edu_readl(ctrl, EDU_CMD);
1951a5d53ad2SKamal Dasu 
1952a5d53ad2SKamal Dasu 	if (wait_for_completion_timeout(&ctrl->edu_done, timeo) <= 0) {
1953a5d53ad2SKamal Dasu 		dev_err(ctrl->dev,
1954a5d53ad2SKamal Dasu 			"timeout waiting for EDU; status %#x, error status %#x\n",
1955a5d53ad2SKamal Dasu 			edu_readl(ctrl, EDU_STATUS),
1956a5d53ad2SKamal Dasu 			edu_readl(ctrl, EDU_ERR_STATUS));
1957a5d53ad2SKamal Dasu 	}
1958a5d53ad2SKamal Dasu 
1959a5d53ad2SKamal Dasu 	dma_unmap_single(ctrl->dev, pa, len, dir);
1960a5d53ad2SKamal Dasu 
1961a0719126SKamal Dasu 	/* read last subpage oob */
1962a0719126SKamal Dasu 	if (ctrl->oob && (ctrl->edu_cmd == EDU_CMD_READ)) {
1963a0719126SKamal Dasu 		ctrl->oob += read_oob_from_regs(ctrl,
1964a0719126SKamal Dasu 						1,
1965a0719126SKamal Dasu 						ctrl->oob, ctrl->sas,
1966a0719126SKamal Dasu 						ctrl->sector_size_1k);
1967a0719126SKamal Dasu 	}
1968a0719126SKamal Dasu 
1969a5d53ad2SKamal Dasu 	/* for program page check NAND status */
1970a5d53ad2SKamal Dasu 	if (((brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS) &
1971a5d53ad2SKamal Dasu 	      INTFC_FLASH_STATUS) & NAND_STATUS_FAIL) &&
1972a5d53ad2SKamal Dasu 	    edu_cmd == EDU_CMD_WRITE) {
1973a5d53ad2SKamal Dasu 		dev_info(ctrl->dev, "program failed at %llx\n",
1974a5d53ad2SKamal Dasu 			 (unsigned long long)addr);
1975a5d53ad2SKamal Dasu 		ret = -EIO;
1976a5d53ad2SKamal Dasu 	}
1977a5d53ad2SKamal Dasu 
1978a5d53ad2SKamal Dasu 	/* Make sure the EDU status is clean */
1979a5d53ad2SKamal Dasu 	if (edu_readl(ctrl, EDU_STATUS) & EDU_STATUS_ACTIVE)
1980a5d53ad2SKamal Dasu 		dev_warn(ctrl->dev, "EDU still active: %#x\n",
1981a5d53ad2SKamal Dasu 			 edu_readl(ctrl, EDU_STATUS));
1982a5d53ad2SKamal Dasu 
1983a5d53ad2SKamal Dasu 	if (unlikely(edu_readl(ctrl, EDU_ERR_STATUS) & EDU_ERR_STATUS_ERRACK)) {
1984a5d53ad2SKamal Dasu 		dev_warn(ctrl->dev, "EDU RBUS error at addr %llx\n",
1985a5d53ad2SKamal Dasu 			 (unsigned long long)addr);
1986a5d53ad2SKamal Dasu 		ret = -EIO;
1987a5d53ad2SKamal Dasu 	}
1988a5d53ad2SKamal Dasu 
1989a5d53ad2SKamal Dasu 	ctrl->edu_pending = false;
1990a5d53ad2SKamal Dasu 	brcmnand_edu_init(ctrl);
1991a5d53ad2SKamal Dasu 	edu_writel(ctrl, EDU_STOP, 0); /* force stop */
1992a5d53ad2SKamal Dasu 	edu_readl(ctrl, EDU_STOP);
1993a5d53ad2SKamal Dasu 
19944551e78aSKamal Dasu 	if (!ret && edu_cmd == EDU_CMD_READ) {
19954551e78aSKamal Dasu 		u64 err_addr = 0;
19964551e78aSKamal Dasu 
19974551e78aSKamal Dasu 		/*
19984551e78aSKamal Dasu 		 * check for ECC errors here, subpage ECC errors are
19994551e78aSKamal Dasu 		 * retained in ECC error address register
20004551e78aSKamal Dasu 		 */
20014551e78aSKamal Dasu 		err_addr = brcmnand_get_uncorrecc_addr(ctrl);
20024551e78aSKamal Dasu 		if (!err_addr) {
20034551e78aSKamal Dasu 			err_addr = brcmnand_get_correcc_addr(ctrl);
20044551e78aSKamal Dasu 			if (err_addr)
20054551e78aSKamal Dasu 				ret = -EUCLEAN;
20064551e78aSKamal Dasu 		} else
20074551e78aSKamal Dasu 			ret = -EBADMSG;
20084551e78aSKamal Dasu 	}
20094551e78aSKamal Dasu 
2010a5d53ad2SKamal Dasu 	return ret;
2011a5d53ad2SKamal Dasu }
2012a5d53ad2SKamal Dasu 
2013fa985e22SLee Jones /*
201427c5b17cSBrian Norris  * Construct a FLASH_DMA descriptor as part of a linked list. You must know the
201527c5b17cSBrian Norris  * following ahead of time:
201627c5b17cSBrian Norris  *  - Is this descriptor the beginning or end of a linked list?
201727c5b17cSBrian Norris  *  - What is the (DMA) address of the next descriptor in the linked list?
201827c5b17cSBrian Norris  */
201927c5b17cSBrian Norris static int brcmnand_fill_dma_desc(struct brcmnand_host *host,
202027c5b17cSBrian Norris 				  struct brcm_nand_dma_desc *desc, u64 addr,
202127c5b17cSBrian Norris 				  dma_addr_t buf, u32 len, u8 dma_cmd,
202227c5b17cSBrian Norris 				  bool begin, bool end,
202327c5b17cSBrian Norris 				  dma_addr_t next_desc)
202427c5b17cSBrian Norris {
202527c5b17cSBrian Norris 	memset(desc, 0, sizeof(*desc));
202627c5b17cSBrian Norris 	/* Descriptors are written in native byte order (wordwise) */
202727c5b17cSBrian Norris 	desc->next_desc = lower_32_bits(next_desc);
202827c5b17cSBrian Norris 	desc->next_desc_ext = upper_32_bits(next_desc);
202927c5b17cSBrian Norris 	desc->cmd_irq = (dma_cmd << 24) |
203027c5b17cSBrian Norris 		(end ? (0x03 << 8) : 0) | /* IRQ | STOP */
203127c5b17cSBrian Norris 		(!!begin) | ((!!end) << 1); /* head, tail */
203227c5b17cSBrian Norris #ifdef CONFIG_CPU_BIG_ENDIAN
203327c5b17cSBrian Norris 	desc->cmd_irq |= 0x01 << 12;
203427c5b17cSBrian Norris #endif
203527c5b17cSBrian Norris 	desc->dram_addr = lower_32_bits(buf);
203627c5b17cSBrian Norris 	desc->dram_addr_ext = upper_32_bits(buf);
203727c5b17cSBrian Norris 	desc->tfr_len = len;
203827c5b17cSBrian Norris 	desc->total_len = len;
203927c5b17cSBrian Norris 	desc->flash_addr = lower_32_bits(addr);
204027c5b17cSBrian Norris 	desc->flash_addr_ext = upper_32_bits(addr);
204127c5b17cSBrian Norris 	desc->cs = host->cs;
204227c5b17cSBrian Norris 	desc->status_valid = 0x01;
204327c5b17cSBrian Norris 	return 0;
204427c5b17cSBrian Norris }
204527c5b17cSBrian Norris 
2046fa985e22SLee Jones /*
204727c5b17cSBrian Norris  * Kick the FLASH_DMA engine, with a given DMA descriptor
204827c5b17cSBrian Norris  */
204927c5b17cSBrian Norris static void brcmnand_dma_run(struct brcmnand_host *host, dma_addr_t desc)
205027c5b17cSBrian Norris {
205127c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
205227c5b17cSBrian Norris 	unsigned long timeo = msecs_to_jiffies(100);
205327c5b17cSBrian Norris 
205427c5b17cSBrian Norris 	flash_dma_writel(ctrl, FLASH_DMA_FIRST_DESC, lower_32_bits(desc));
205527c5b17cSBrian Norris 	(void)flash_dma_readl(ctrl, FLASH_DMA_FIRST_DESC);
205683156c1cSKamal Dasu 	if (ctrl->nand_version > 0x0602) {
205783156c1cSKamal Dasu 		flash_dma_writel(ctrl, FLASH_DMA_FIRST_DESC_EXT,
205883156c1cSKamal Dasu 				 upper_32_bits(desc));
205927c5b17cSBrian Norris 		(void)flash_dma_readl(ctrl, FLASH_DMA_FIRST_DESC_EXT);
206083156c1cSKamal Dasu 	}
206127c5b17cSBrian Norris 
206227c5b17cSBrian Norris 	/* Start FLASH_DMA engine */
206327c5b17cSBrian Norris 	ctrl->dma_pending = true;
206427c5b17cSBrian Norris 	mb(); /* flush previous writes */
206527c5b17cSBrian Norris 	flash_dma_writel(ctrl, FLASH_DMA_CTRL, 0x03); /* wake | run */
206627c5b17cSBrian Norris 
206727c5b17cSBrian Norris 	if (wait_for_completion_timeout(&ctrl->dma_done, timeo) <= 0) {
206827c5b17cSBrian Norris 		dev_err(ctrl->dev,
206927c5b17cSBrian Norris 				"timeout waiting for DMA; status %#x, error status %#x\n",
207027c5b17cSBrian Norris 				flash_dma_readl(ctrl, FLASH_DMA_STATUS),
207127c5b17cSBrian Norris 				flash_dma_readl(ctrl, FLASH_DMA_ERROR_STATUS));
207227c5b17cSBrian Norris 	}
207327c5b17cSBrian Norris 	ctrl->dma_pending = false;
207427c5b17cSBrian Norris 	flash_dma_writel(ctrl, FLASH_DMA_CTRL, 0); /* force stop */
207527c5b17cSBrian Norris }
207627c5b17cSBrian Norris 
207727c5b17cSBrian Norris static int brcmnand_dma_trans(struct brcmnand_host *host, u64 addr, u32 *buf,
2078a0719126SKamal Dasu 			      u8 *oob, u32 len, u8 dma_cmd)
207927c5b17cSBrian Norris {
208027c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
208127c5b17cSBrian Norris 	dma_addr_t buf_pa;
208227c5b17cSBrian Norris 	int dir = dma_cmd == CMD_PAGE_READ ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
208327c5b17cSBrian Norris 
208427c5b17cSBrian Norris 	buf_pa = dma_map_single(ctrl->dev, buf, len, dir);
208527c5b17cSBrian Norris 	if (dma_mapping_error(ctrl->dev, buf_pa)) {
208627c5b17cSBrian Norris 		dev_err(ctrl->dev, "unable to map buffer for DMA\n");
208727c5b17cSBrian Norris 		return -ENOMEM;
208827c5b17cSBrian Norris 	}
208927c5b17cSBrian Norris 
209027c5b17cSBrian Norris 	brcmnand_fill_dma_desc(host, ctrl->dma_desc, addr, buf_pa, len,
209127c5b17cSBrian Norris 				   dma_cmd, true, true, 0);
209227c5b17cSBrian Norris 
209327c5b17cSBrian Norris 	brcmnand_dma_run(host, ctrl->dma_pa);
209427c5b17cSBrian Norris 
209527c5b17cSBrian Norris 	dma_unmap_single(ctrl->dev, buf_pa, len, dir);
209627c5b17cSBrian Norris 
209727c5b17cSBrian Norris 	if (ctrl->dma_desc->status_valid & FLASH_DMA_ECC_ERROR)
209827c5b17cSBrian Norris 		return -EBADMSG;
209927c5b17cSBrian Norris 	else if (ctrl->dma_desc->status_valid & FLASH_DMA_CORR_ERROR)
210027c5b17cSBrian Norris 		return -EUCLEAN;
210127c5b17cSBrian Norris 
210227c5b17cSBrian Norris 	return 0;
210327c5b17cSBrian Norris }
210427c5b17cSBrian Norris 
210527c5b17cSBrian Norris /*
210627c5b17cSBrian Norris  * Assumes proper CS is already set
210727c5b17cSBrian Norris  */
210827c5b17cSBrian Norris static int brcmnand_read_by_pio(struct mtd_info *mtd, struct nand_chip *chip,
210927c5b17cSBrian Norris 				u64 addr, unsigned int trans, u32 *buf,
211027c5b17cSBrian Norris 				u8 *oob, u64 *err_addr)
211127c5b17cSBrian Norris {
2112d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
211327c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
211427c5b17cSBrian Norris 	int i, j, ret = 0;
211527c5b17cSBrian Norris 
21163c7c1e45SKamal Dasu 	brcmnand_clear_ecc_addr(ctrl);
211727c5b17cSBrian Norris 
211827c5b17cSBrian Norris 	for (i = 0; i < trans; i++, addr += FC_BYTES) {
21193c7c1e45SKamal Dasu 		brcmnand_set_cmd_addr(mtd, addr);
212027c5b17cSBrian Norris 		/* SPARE_AREA_READ does not use ECC, so just use PAGE_READ */
212127c5b17cSBrian Norris 		brcmnand_send_cmd(host, CMD_PAGE_READ);
2122f1d46942SBoris Brezillon 		brcmnand_waitfunc(chip);
212327c5b17cSBrian Norris 
2124c26211d3SBrian Norris 		if (likely(buf)) {
2125eab7fdc7SRay Jui 			brcmnand_soc_data_bus_prepare(ctrl->soc, false);
2126c26211d3SBrian Norris 
212727c5b17cSBrian Norris 			for (j = 0; j < FC_WORDS; j++, buf++)
212827c5b17cSBrian Norris 				*buf = brcmnand_read_fc(ctrl, j);
212927c5b17cSBrian Norris 
2130eab7fdc7SRay Jui 			brcmnand_soc_data_bus_unprepare(ctrl->soc, false);
2131c26211d3SBrian Norris 		}
2132c26211d3SBrian Norris 
213327c5b17cSBrian Norris 		if (oob)
213427c5b17cSBrian Norris 			oob += read_oob_from_regs(ctrl, i, oob,
213527c5b17cSBrian Norris 					mtd->oobsize / trans,
213627c5b17cSBrian Norris 					host->hwcfg.sector_size_1k);
213727c5b17cSBrian Norris 
213836415a79Sdavid regan 		if (ret != -EBADMSG) {
21393c7c1e45SKamal Dasu 			*err_addr = brcmnand_get_uncorrecc_addr(ctrl);
21403c7c1e45SKamal Dasu 
214127c5b17cSBrian Norris 			if (*err_addr)
214227c5b17cSBrian Norris 				ret = -EBADMSG;
214327c5b17cSBrian Norris 		}
214427c5b17cSBrian Norris 
214527c5b17cSBrian Norris 		if (!ret) {
21463c7c1e45SKamal Dasu 			*err_addr = brcmnand_get_correcc_addr(ctrl);
21473c7c1e45SKamal Dasu 
214827c5b17cSBrian Norris 			if (*err_addr)
214927c5b17cSBrian Norris 				ret = -EUCLEAN;
215027c5b17cSBrian Norris 		}
215127c5b17cSBrian Norris 	}
215227c5b17cSBrian Norris 
215327c5b17cSBrian Norris 	return ret;
215427c5b17cSBrian Norris }
215527c5b17cSBrian Norris 
215602b88eeaSKamal Dasu /*
215702b88eeaSKamal Dasu  * Check a page to see if it is erased (w/ bitflips) after an uncorrectable ECC
215802b88eeaSKamal Dasu  * error
215902b88eeaSKamal Dasu  *
216002b88eeaSKamal Dasu  * Because the HW ECC signals an ECC error if an erase paged has even a single
216102b88eeaSKamal Dasu  * bitflip, we must check each ECC error to see if it is actually an erased
216202b88eeaSKamal Dasu  * page with bitflips, not a truly corrupted page.
216302b88eeaSKamal Dasu  *
216402b88eeaSKamal Dasu  * On a real error, return a negative error code (-EBADMSG for ECC error), and
216502b88eeaSKamal Dasu  * buf will contain raw data.
216602b88eeaSKamal Dasu  * Otherwise, buf gets filled with 0xffs and return the maximum number of
216702b88eeaSKamal Dasu  * bitflips-per-ECC-sector to the caller.
216802b88eeaSKamal Dasu  *
216902b88eeaSKamal Dasu  */
217002b88eeaSKamal Dasu static int brcmstb_nand_verify_erased_page(struct mtd_info *mtd,
217102b88eeaSKamal Dasu 		  struct nand_chip *chip, void *buf, u64 addr)
217202b88eeaSKamal Dasu {
2173dcb351c0SÁlvaro Fernández Rojas 	struct mtd_oob_region ecc;
2174dcb351c0SÁlvaro Fernández Rojas 	int i;
217502b88eeaSKamal Dasu 	int bitflips = 0;
217602b88eeaSKamal Dasu 	int page = addr >> chip->page_shift;
217702b88eeaSKamal Dasu 	int ret;
2178dcb351c0SÁlvaro Fernández Rojas 	void *ecc_bytes;
21797f852cc1SClaire Lin 	void *ecc_chunk;
218002b88eeaSKamal Dasu 
2181eeab7174SBoris Brezillon 	if (!buf)
2182eeab7174SBoris Brezillon 		buf = nand_get_data_buf(chip);
218302b88eeaSKamal Dasu 
218402b88eeaSKamal Dasu 	/* read without ecc for verification */
2185b9761687SBoris Brezillon 	ret = chip->ecc.read_page_raw(chip, buf, true, page);
218602b88eeaSKamal Dasu 	if (ret)
218702b88eeaSKamal Dasu 		return ret;
218802b88eeaSKamal Dasu 
2189dcb351c0SÁlvaro Fernández Rojas 	for (i = 0; i < chip->ecc.steps; i++) {
21907f852cc1SClaire Lin 		ecc_chunk = buf + chip->ecc.size * i;
2191dcb351c0SÁlvaro Fernández Rojas 
2192dcb351c0SÁlvaro Fernández Rojas 		mtd_ooblayout_ecc(mtd, i, &ecc);
2193dcb351c0SÁlvaro Fernández Rojas 		ecc_bytes = chip->oob_poi + ecc.offset;
2194dcb351c0SÁlvaro Fernández Rojas 
2195dcb351c0SÁlvaro Fernández Rojas 		ret = nand_check_erased_ecc_chunk(ecc_chunk, chip->ecc.size,
2196dcb351c0SÁlvaro Fernández Rojas 						  ecc_bytes, ecc.length,
2197dcb351c0SÁlvaro Fernández Rojas 						  NULL, 0,
219802b88eeaSKamal Dasu 						  chip->ecc.strength);
219902b88eeaSKamal Dasu 		if (ret < 0)
220002b88eeaSKamal Dasu 			return ret;
220102b88eeaSKamal Dasu 
220202b88eeaSKamal Dasu 		bitflips = max(bitflips, ret);
220302b88eeaSKamal Dasu 	}
220402b88eeaSKamal Dasu 
220502b88eeaSKamal Dasu 	return bitflips;
220602b88eeaSKamal Dasu }
220702b88eeaSKamal Dasu 
220827c5b17cSBrian Norris static int brcmnand_read(struct mtd_info *mtd, struct nand_chip *chip,
220927c5b17cSBrian Norris 			 u64 addr, unsigned int trans, u32 *buf, u8 *oob)
221027c5b17cSBrian Norris {
2211d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
221227c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
221327c5b17cSBrian Norris 	u64 err_addr = 0;
221427c5b17cSBrian Norris 	int err;
2215bc265323SKamal Dasu 	bool retry = true;
22164551e78aSKamal Dasu 	bool edu_err = false;
221727c5b17cSBrian Norris 
221827c5b17cSBrian Norris 	dev_dbg(ctrl->dev, "read %llx -> %p\n", (unsigned long long)addr, buf);
221927c5b17cSBrian Norris 
2220bc265323SKamal Dasu try_dmaread:
22213c7c1e45SKamal Dasu 	brcmnand_clear_ecc_addr(ctrl);
222227c5b17cSBrian Norris 
2223a0719126SKamal Dasu 	if (ctrl->dma_trans && (has_edu(ctrl) || !oob) &&
2224a0719126SKamal Dasu 	    flash_dma_buf_ok(buf)) {
2225a0719126SKamal Dasu 		err = ctrl->dma_trans(host, addr, buf, oob,
2226a5d53ad2SKamal Dasu 				      trans * FC_BYTES,
222727c5b17cSBrian Norris 				      CMD_PAGE_READ);
2228a5d53ad2SKamal Dasu 
222927c5b17cSBrian Norris 		if (err) {
223027c5b17cSBrian Norris 			if (mtd_is_bitflip_or_eccerr(err))
223127c5b17cSBrian Norris 				err_addr = addr;
223227c5b17cSBrian Norris 			else
223327c5b17cSBrian Norris 				return -EIO;
223427c5b17cSBrian Norris 		}
22354551e78aSKamal Dasu 
22364551e78aSKamal Dasu 		if (has_edu(ctrl) && err_addr)
22374551e78aSKamal Dasu 			edu_err = true;
22384551e78aSKamal Dasu 
223927c5b17cSBrian Norris 	} else {
224027c5b17cSBrian Norris 		if (oob)
224127c5b17cSBrian Norris 			memset(oob, 0x99, mtd->oobsize);
224227c5b17cSBrian Norris 
224327c5b17cSBrian Norris 		err = brcmnand_read_by_pio(mtd, chip, addr, trans, buf,
224427c5b17cSBrian Norris 					       oob, &err_addr);
224527c5b17cSBrian Norris 	}
224627c5b17cSBrian Norris 
224727c5b17cSBrian Norris 	if (mtd_is_eccerr(err)) {
224802b88eeaSKamal Dasu 		/*
2249bc265323SKamal Dasu 		 * On controller version and 7.0, 7.1 , DMA read after a
2250bc265323SKamal Dasu 		 * prior PIO read that reported uncorrectable error,
2251bc265323SKamal Dasu 		 * the DMA engine captures this error following DMA read
2252bc265323SKamal Dasu 		 * cleared only on subsequent DMA read, so just retry once
2253bc265323SKamal Dasu 		 * to clear a possible false error reported for current DMA
2254bc265323SKamal Dasu 		 * read
2255bc265323SKamal Dasu 		 */
2256bc265323SKamal Dasu 		if ((ctrl->nand_version == 0x0700) ||
2257bc265323SKamal Dasu 		    (ctrl->nand_version == 0x0701)) {
2258bc265323SKamal Dasu 			if (retry) {
2259bc265323SKamal Dasu 				retry = false;
2260bc265323SKamal Dasu 				goto try_dmaread;
2261bc265323SKamal Dasu 			}
2262bc265323SKamal Dasu 		}
2263bc265323SKamal Dasu 
2264bc265323SKamal Dasu 		/*
226502b88eeaSKamal Dasu 		 * Controller version 7.2 has hw encoder to detect erased page
226602b88eeaSKamal Dasu 		 * bitflips, apply sw verification for older controllers only
226702b88eeaSKamal Dasu 		 */
226802b88eeaSKamal Dasu 		if (ctrl->nand_version < 0x0702) {
226902b88eeaSKamal Dasu 			err = brcmstb_nand_verify_erased_page(mtd, chip, buf,
227002b88eeaSKamal Dasu 							      addr);
227102b88eeaSKamal Dasu 			/* erased page bitflips corrected */
2272e44b9a9cSAlbert Hsieh 			if (err >= 0)
227302b88eeaSKamal Dasu 				return err;
227402b88eeaSKamal Dasu 		}
227502b88eeaSKamal Dasu 
227627c5b17cSBrian Norris 		dev_dbg(ctrl->dev, "uncorrectable error at 0x%llx\n",
227727c5b17cSBrian Norris 			(unsigned long long)err_addr);
227827c5b17cSBrian Norris 		mtd->ecc_stats.failed++;
227927c5b17cSBrian Norris 		/* NAND layer expects zero on ECC errors */
228027c5b17cSBrian Norris 		return 0;
228127c5b17cSBrian Norris 	}
228227c5b17cSBrian Norris 
228327c5b17cSBrian Norris 	if (mtd_is_bitflip(err)) {
228427c5b17cSBrian Norris 		unsigned int corrected = brcmnand_count_corrected(ctrl);
228527c5b17cSBrian Norris 
22864551e78aSKamal Dasu 		/* in case of EDU correctable error we read again using PIO */
22874551e78aSKamal Dasu 		if (edu_err)
22884551e78aSKamal Dasu 			err = brcmnand_read_by_pio(mtd, chip, addr, trans, buf,
22894551e78aSKamal Dasu 						   oob, &err_addr);
22904551e78aSKamal Dasu 
229127c5b17cSBrian Norris 		dev_dbg(ctrl->dev, "corrected error at 0x%llx\n",
229227c5b17cSBrian Norris 			(unsigned long long)err_addr);
229327c5b17cSBrian Norris 		mtd->ecc_stats.corrected += corrected;
229427c5b17cSBrian Norris 		/* Always exceed the software-imposed threshold */
229527c5b17cSBrian Norris 		return max(mtd->bitflip_threshold, corrected);
229627c5b17cSBrian Norris 	}
229727c5b17cSBrian Norris 
229827c5b17cSBrian Norris 	return 0;
229927c5b17cSBrian Norris }
230027c5b17cSBrian Norris 
2301b9761687SBoris Brezillon static int brcmnand_read_page(struct nand_chip *chip, uint8_t *buf,
2302b9761687SBoris Brezillon 			      int oob_required, int page)
230327c5b17cSBrian Norris {
2304b9761687SBoris Brezillon 	struct mtd_info *mtd = nand_to_mtd(chip);
2305d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
230627c5b17cSBrian Norris 	u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL;
230727c5b17cSBrian Norris 
230825f815f6SBoris Brezillon 	nand_read_page_op(chip, page, 0, NULL, 0);
230925f815f6SBoris Brezillon 
231027c5b17cSBrian Norris 	return brcmnand_read(mtd, chip, host->last_addr,
231127c5b17cSBrian Norris 			mtd->writesize >> FC_SHIFT, (u32 *)buf, oob);
231227c5b17cSBrian Norris }
231327c5b17cSBrian Norris 
2314b9761687SBoris Brezillon static int brcmnand_read_page_raw(struct nand_chip *chip, uint8_t *buf,
2315b9761687SBoris Brezillon 				  int oob_required, int page)
231627c5b17cSBrian Norris {
2317d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
2318b9761687SBoris Brezillon 	struct mtd_info *mtd = nand_to_mtd(chip);
231927c5b17cSBrian Norris 	u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL;
232027c5b17cSBrian Norris 	int ret;
232127c5b17cSBrian Norris 
232225f815f6SBoris Brezillon 	nand_read_page_op(chip, page, 0, NULL, 0);
232325f815f6SBoris Brezillon 
232427c5b17cSBrian Norris 	brcmnand_set_ecc_enabled(host, 0);
232527c5b17cSBrian Norris 	ret = brcmnand_read(mtd, chip, host->last_addr,
232627c5b17cSBrian Norris 			mtd->writesize >> FC_SHIFT, (u32 *)buf, oob);
232727c5b17cSBrian Norris 	brcmnand_set_ecc_enabled(host, 1);
232827c5b17cSBrian Norris 	return ret;
232927c5b17cSBrian Norris }
233027c5b17cSBrian Norris 
2331b9761687SBoris Brezillon static int brcmnand_read_oob(struct nand_chip *chip, int page)
233227c5b17cSBrian Norris {
2333b9761687SBoris Brezillon 	struct mtd_info *mtd = nand_to_mtd(chip);
2334b9761687SBoris Brezillon 
233527c5b17cSBrian Norris 	return brcmnand_read(mtd, chip, (u64)page << chip->page_shift,
233627c5b17cSBrian Norris 			mtd->writesize >> FC_SHIFT,
233727c5b17cSBrian Norris 			NULL, (u8 *)chip->oob_poi);
233827c5b17cSBrian Norris }
233927c5b17cSBrian Norris 
2340b9761687SBoris Brezillon static int brcmnand_read_oob_raw(struct nand_chip *chip, int page)
234127c5b17cSBrian Norris {
2342b9761687SBoris Brezillon 	struct mtd_info *mtd = nand_to_mtd(chip);
2343d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
234427c5b17cSBrian Norris 
234527c5b17cSBrian Norris 	brcmnand_set_ecc_enabled(host, 0);
234627c5b17cSBrian Norris 	brcmnand_read(mtd, chip, (u64)page << chip->page_shift,
234727c5b17cSBrian Norris 		mtd->writesize >> FC_SHIFT,
234827c5b17cSBrian Norris 		NULL, (u8 *)chip->oob_poi);
234927c5b17cSBrian Norris 	brcmnand_set_ecc_enabled(host, 1);
235027c5b17cSBrian Norris 	return 0;
235127c5b17cSBrian Norris }
235227c5b17cSBrian Norris 
235327c5b17cSBrian Norris static int brcmnand_write(struct mtd_info *mtd, struct nand_chip *chip,
235427c5b17cSBrian Norris 			  u64 addr, const u32 *buf, u8 *oob)
235527c5b17cSBrian Norris {
2356d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
235727c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
235827c5b17cSBrian Norris 	unsigned int i, j, trans = mtd->writesize >> FC_SHIFT;
235927c5b17cSBrian Norris 	int status, ret = 0;
236027c5b17cSBrian Norris 
236127c5b17cSBrian Norris 	dev_dbg(ctrl->dev, "write %llx <- %p\n", (unsigned long long)addr, buf);
236227c5b17cSBrian Norris 
23633f08b8baSAnup Patel 	if (unlikely((unsigned long)buf & 0x03)) {
236427c5b17cSBrian Norris 		dev_warn(ctrl->dev, "unaligned buffer: %p\n", buf);
23653f08b8baSAnup Patel 		buf = (u32 *)((unsigned long)buf & ~0x03);
236627c5b17cSBrian Norris 	}
236727c5b17cSBrian Norris 
236827c5b17cSBrian Norris 	brcmnand_wp(mtd, 0);
236927c5b17cSBrian Norris 
237027c5b17cSBrian Norris 	for (i = 0; i < ctrl->max_oob; i += 4)
237127c5b17cSBrian Norris 		oob_reg_write(ctrl, i, 0xffffffff);
237227c5b17cSBrian Norris 
237322ca05b8SKamal Dasu 	if (mtd->oops_panic_write)
237422ca05b8SKamal Dasu 		/* switch to interrupt polling and PIO mode */
237522ca05b8SKamal Dasu 		disable_ctrl_irqs(ctrl);
237622ca05b8SKamal Dasu 
2377a0719126SKamal Dasu 	if (use_dma(ctrl) && (has_edu(ctrl) || !oob) && flash_dma_buf_ok(buf)) {
2378a0719126SKamal Dasu 		if (ctrl->dma_trans(host, addr, (u32 *)buf, oob, mtd->writesize,
2379a5d53ad2SKamal Dasu 				    CMD_PROGRAM_PAGE))
2380a5d53ad2SKamal Dasu 
238127c5b17cSBrian Norris 			ret = -EIO;
2382a5d53ad2SKamal Dasu 
238327c5b17cSBrian Norris 		goto out;
238427c5b17cSBrian Norris 	}
238527c5b17cSBrian Norris 
238627c5b17cSBrian Norris 	for (i = 0; i < trans; i++, addr += FC_BYTES) {
238727c5b17cSBrian Norris 		/* full address MUST be set before populating FC */
23883c7c1e45SKamal Dasu 		brcmnand_set_cmd_addr(mtd, addr);
238927c5b17cSBrian Norris 
2390c26211d3SBrian Norris 		if (buf) {
2391eab7fdc7SRay Jui 			brcmnand_soc_data_bus_prepare(ctrl->soc, false);
2392c26211d3SBrian Norris 
239327c5b17cSBrian Norris 			for (j = 0; j < FC_WORDS; j++, buf++)
239427c5b17cSBrian Norris 				brcmnand_write_fc(ctrl, j, *buf);
2395c26211d3SBrian Norris 
2396eab7fdc7SRay Jui 			brcmnand_soc_data_bus_unprepare(ctrl->soc, false);
2397c26211d3SBrian Norris 		} else if (oob) {
239827c5b17cSBrian Norris 			for (j = 0; j < FC_WORDS; j++)
239927c5b17cSBrian Norris 				brcmnand_write_fc(ctrl, j, 0xffffffff);
2400c26211d3SBrian Norris 		}
240127c5b17cSBrian Norris 
240227c5b17cSBrian Norris 		if (oob) {
240327c5b17cSBrian Norris 			oob += write_oob_to_regs(ctrl, i, oob,
240427c5b17cSBrian Norris 					mtd->oobsize / trans,
240527c5b17cSBrian Norris 					host->hwcfg.sector_size_1k);
240627c5b17cSBrian Norris 		}
240727c5b17cSBrian Norris 
240827c5b17cSBrian Norris 		/* we cannot use SPARE_AREA_PROGRAM when PARTIAL_PAGE_EN=0 */
240927c5b17cSBrian Norris 		brcmnand_send_cmd(host, CMD_PROGRAM_PAGE);
2410f1d46942SBoris Brezillon 		status = brcmnand_waitfunc(chip);
241127c5b17cSBrian Norris 
241227c5b17cSBrian Norris 		if (status & NAND_STATUS_FAIL) {
241327c5b17cSBrian Norris 			dev_info(ctrl->dev, "program failed at %llx\n",
241427c5b17cSBrian Norris 				(unsigned long long)addr);
241527c5b17cSBrian Norris 			ret = -EIO;
241627c5b17cSBrian Norris 			goto out;
241727c5b17cSBrian Norris 		}
241827c5b17cSBrian Norris 	}
241927c5b17cSBrian Norris out:
242027c5b17cSBrian Norris 	brcmnand_wp(mtd, 1);
242127c5b17cSBrian Norris 	return ret;
242227c5b17cSBrian Norris }
242327c5b17cSBrian Norris 
2424767eb6fbSBoris Brezillon static int brcmnand_write_page(struct nand_chip *chip, const uint8_t *buf,
2425767eb6fbSBoris Brezillon 			       int oob_required, int page)
242627c5b17cSBrian Norris {
2427767eb6fbSBoris Brezillon 	struct mtd_info *mtd = nand_to_mtd(chip);
2428d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
242927c5b17cSBrian Norris 	void *oob = oob_required ? chip->oob_poi : NULL;
243027c5b17cSBrian Norris 
243125f815f6SBoris Brezillon 	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
243227c5b17cSBrian Norris 	brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
243325f815f6SBoris Brezillon 
243425f815f6SBoris Brezillon 	return nand_prog_page_end_op(chip);
243527c5b17cSBrian Norris }
243627c5b17cSBrian Norris 
2437767eb6fbSBoris Brezillon static int brcmnand_write_page_raw(struct nand_chip *chip, const uint8_t *buf,
243845aaeff9SBoris BREZILLON 				   int oob_required, int page)
243927c5b17cSBrian Norris {
2440767eb6fbSBoris Brezillon 	struct mtd_info *mtd = nand_to_mtd(chip);
2441d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
244227c5b17cSBrian Norris 	void *oob = oob_required ? chip->oob_poi : NULL;
244327c5b17cSBrian Norris 
244425f815f6SBoris Brezillon 	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
244527c5b17cSBrian Norris 	brcmnand_set_ecc_enabled(host, 0);
244627c5b17cSBrian Norris 	brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
244727c5b17cSBrian Norris 	brcmnand_set_ecc_enabled(host, 1);
244825f815f6SBoris Brezillon 
244925f815f6SBoris Brezillon 	return nand_prog_page_end_op(chip);
245027c5b17cSBrian Norris }
245127c5b17cSBrian Norris 
2452767eb6fbSBoris Brezillon static int brcmnand_write_oob(struct nand_chip *chip, int page)
245327c5b17cSBrian Norris {
2454767eb6fbSBoris Brezillon 	return brcmnand_write(nand_to_mtd(chip), chip,
2455767eb6fbSBoris Brezillon 			      (u64)page << chip->page_shift, NULL,
2456767eb6fbSBoris Brezillon 			      chip->oob_poi);
245727c5b17cSBrian Norris }
245827c5b17cSBrian Norris 
2459767eb6fbSBoris Brezillon static int brcmnand_write_oob_raw(struct nand_chip *chip, int page)
246027c5b17cSBrian Norris {
2461767eb6fbSBoris Brezillon 	struct mtd_info *mtd = nand_to_mtd(chip);
2462d699ed25SBoris BREZILLON 	struct brcmnand_host *host = nand_get_controller_data(chip);
246327c5b17cSBrian Norris 	int ret;
246427c5b17cSBrian Norris 
246527c5b17cSBrian Norris 	brcmnand_set_ecc_enabled(host, 0);
246627c5b17cSBrian Norris 	ret = brcmnand_write(mtd, chip, (u64)page << chip->page_shift, NULL,
246727c5b17cSBrian Norris 				 (u8 *)chip->oob_poi);
246827c5b17cSBrian Norris 	brcmnand_set_ecc_enabled(host, 1);
246927c5b17cSBrian Norris 
247027c5b17cSBrian Norris 	return ret;
247127c5b17cSBrian Norris }
247227c5b17cSBrian Norris 
247327c5b17cSBrian Norris /***********************************************************************
247427c5b17cSBrian Norris  * Per-CS setup (1 NAND device)
247527c5b17cSBrian Norris  ***********************************************************************/
247627c5b17cSBrian Norris 
247727c5b17cSBrian Norris static int brcmnand_set_cfg(struct brcmnand_host *host,
247827c5b17cSBrian Norris 			    struct brcmnand_cfg *cfg)
247927c5b17cSBrian Norris {
248027c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
248127c5b17cSBrian Norris 	struct nand_chip *chip = &host->chip;
248227c5b17cSBrian Norris 	u16 cfg_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_CFG);
248327c5b17cSBrian Norris 	u16 cfg_ext_offs = brcmnand_cs_offset(ctrl, host->cs,
248427c5b17cSBrian Norris 			BRCMNAND_CS_CFG_EXT);
248527c5b17cSBrian Norris 	u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs,
248627c5b17cSBrian Norris 			BRCMNAND_CS_ACC_CONTROL);
248727c5b17cSBrian Norris 	u8 block_size = 0, page_size = 0, device_size = 0;
248827c5b17cSBrian Norris 	u32 tmp;
248927c5b17cSBrian Norris 
249027c5b17cSBrian Norris 	if (ctrl->block_sizes) {
249127c5b17cSBrian Norris 		int i, found;
249227c5b17cSBrian Norris 
249327c5b17cSBrian Norris 		for (i = 0, found = 0; ctrl->block_sizes[i]; i++)
249427c5b17cSBrian Norris 			if (ctrl->block_sizes[i] * 1024 == cfg->block_size) {
249527c5b17cSBrian Norris 				block_size = i;
249627c5b17cSBrian Norris 				found = 1;
249727c5b17cSBrian Norris 			}
249827c5b17cSBrian Norris 		if (!found) {
249927c5b17cSBrian Norris 			dev_warn(ctrl->dev, "invalid block size %u\n",
250027c5b17cSBrian Norris 					cfg->block_size);
250127c5b17cSBrian Norris 			return -EINVAL;
250227c5b17cSBrian Norris 		}
250327c5b17cSBrian Norris 	} else {
250427c5b17cSBrian Norris 		block_size = ffs(cfg->block_size) - ffs(BRCMNAND_MIN_BLOCKSIZE);
250527c5b17cSBrian Norris 	}
250627c5b17cSBrian Norris 
250727c5b17cSBrian Norris 	if (cfg->block_size < BRCMNAND_MIN_BLOCKSIZE || (ctrl->max_block_size &&
250827c5b17cSBrian Norris 				cfg->block_size > ctrl->max_block_size)) {
250927c5b17cSBrian Norris 		dev_warn(ctrl->dev, "invalid block size %u\n",
251027c5b17cSBrian Norris 				cfg->block_size);
251127c5b17cSBrian Norris 		block_size = 0;
251227c5b17cSBrian Norris 	}
251327c5b17cSBrian Norris 
251427c5b17cSBrian Norris 	if (ctrl->page_sizes) {
251527c5b17cSBrian Norris 		int i, found;
251627c5b17cSBrian Norris 
251727c5b17cSBrian Norris 		for (i = 0, found = 0; ctrl->page_sizes[i]; i++)
251827c5b17cSBrian Norris 			if (ctrl->page_sizes[i] == cfg->page_size) {
251927c5b17cSBrian Norris 				page_size = i;
252027c5b17cSBrian Norris 				found = 1;
252127c5b17cSBrian Norris 			}
252227c5b17cSBrian Norris 		if (!found) {
252327c5b17cSBrian Norris 			dev_warn(ctrl->dev, "invalid page size %u\n",
252427c5b17cSBrian Norris 					cfg->page_size);
252527c5b17cSBrian Norris 			return -EINVAL;
252627c5b17cSBrian Norris 		}
252727c5b17cSBrian Norris 	} else {
252827c5b17cSBrian Norris 		page_size = ffs(cfg->page_size) - ffs(BRCMNAND_MIN_PAGESIZE);
252927c5b17cSBrian Norris 	}
253027c5b17cSBrian Norris 
253127c5b17cSBrian Norris 	if (cfg->page_size < BRCMNAND_MIN_PAGESIZE || (ctrl->max_page_size &&
253227c5b17cSBrian Norris 				cfg->page_size > ctrl->max_page_size)) {
253327c5b17cSBrian Norris 		dev_warn(ctrl->dev, "invalid page size %u\n", cfg->page_size);
253427c5b17cSBrian Norris 		return -EINVAL;
253527c5b17cSBrian Norris 	}
253627c5b17cSBrian Norris 
253727c5b17cSBrian Norris 	if (fls64(cfg->device_size) < fls64(BRCMNAND_MIN_DEVSIZE)) {
253827c5b17cSBrian Norris 		dev_warn(ctrl->dev, "invalid device size 0x%llx\n",
253927c5b17cSBrian Norris 			(unsigned long long)cfg->device_size);
254027c5b17cSBrian Norris 		return -EINVAL;
254127c5b17cSBrian Norris 	}
254227c5b17cSBrian Norris 	device_size = fls64(cfg->device_size) - fls64(BRCMNAND_MIN_DEVSIZE);
254327c5b17cSBrian Norris 
25443f06d2a9SBrian Norris 	tmp = (cfg->blk_adr_bytes << CFG_BLK_ADR_BYTES_SHIFT) |
25453f06d2a9SBrian Norris 		(cfg->col_adr_bytes << CFG_COL_ADR_BYTES_SHIFT) |
25463f06d2a9SBrian Norris 		(cfg->ful_adr_bytes << CFG_FUL_ADR_BYTES_SHIFT) |
25473f06d2a9SBrian Norris 		(!!(cfg->device_width == 16) << CFG_BUS_WIDTH_SHIFT) |
25483f06d2a9SBrian Norris 		(device_size << CFG_DEVICE_SIZE_SHIFT);
254927c5b17cSBrian Norris 	if (cfg_offs == cfg_ext_offs) {
25507e7c7df5SÁlvaro Fernández Rojas 		tmp |= (page_size << ctrl->page_size_shift) |
25513f06d2a9SBrian Norris 		       (block_size << CFG_BLK_SIZE_SHIFT);
255227c5b17cSBrian Norris 		nand_writereg(ctrl, cfg_offs, tmp);
255327c5b17cSBrian Norris 	} else {
255427c5b17cSBrian Norris 		nand_writereg(ctrl, cfg_offs, tmp);
25553f06d2a9SBrian Norris 		tmp = (page_size << CFG_EXT_PAGE_SIZE_SHIFT) |
25563f06d2a9SBrian Norris 		      (block_size << CFG_EXT_BLK_SIZE_SHIFT);
255727c5b17cSBrian Norris 		nand_writereg(ctrl, cfg_ext_offs, tmp);
255827c5b17cSBrian Norris 	}
255927c5b17cSBrian Norris 
256027c5b17cSBrian Norris 	tmp = nand_readreg(ctrl, acc_control_offs);
256127c5b17cSBrian Norris 	tmp &= ~brcmnand_ecc_level_mask(ctrl);
256227c5b17cSBrian Norris 	tmp &= ~brcmnand_spare_area_mask(ctrl);
25637e7c7df5SÁlvaro Fernández Rojas 	if (ctrl->nand_version >= 0x0302) {
25647e7c7df5SÁlvaro Fernández Rojas 		tmp |= cfg->ecc_level << NAND_ACC_CONTROL_ECC_SHIFT;
256527c5b17cSBrian Norris 		tmp |= cfg->spare_area_size;
25667e7c7df5SÁlvaro Fernández Rojas 	}
256727c5b17cSBrian Norris 	nand_writereg(ctrl, acc_control_offs, tmp);
256827c5b17cSBrian Norris 
256927c5b17cSBrian Norris 	brcmnand_set_sector_size_1k(host, cfg->sector_size_1k);
257027c5b17cSBrian Norris 
257127c5b17cSBrian Norris 	/* threshold = ceil(BCH-level * 0.75) */
257227c5b17cSBrian Norris 	brcmnand_wr_corr_thresh(host, DIV_ROUND_UP(chip->ecc.strength * 3, 4));
257327c5b17cSBrian Norris 
257427c5b17cSBrian Norris 	return 0;
257527c5b17cSBrian Norris }
257627c5b17cSBrian Norris 
2577decba6d4SFlorian Fainelli static void brcmnand_print_cfg(struct brcmnand_host *host,
2578decba6d4SFlorian Fainelli 			       char *buf, struct brcmnand_cfg *cfg)
257927c5b17cSBrian Norris {
258027c5b17cSBrian Norris 	buf += sprintf(buf,
258127c5b17cSBrian Norris 		"%lluMiB total, %uKiB blocks, %u%s pages, %uB OOB, %u-bit",
258227c5b17cSBrian Norris 		(unsigned long long)cfg->device_size >> 20,
258327c5b17cSBrian Norris 		cfg->block_size >> 10,
258427c5b17cSBrian Norris 		cfg->page_size >= 1024 ? cfg->page_size >> 10 : cfg->page_size,
258527c5b17cSBrian Norris 		cfg->page_size >= 1024 ? "KiB" : "B",
258627c5b17cSBrian Norris 		cfg->spare_area_size, cfg->device_width);
258727c5b17cSBrian Norris 
258827c5b17cSBrian Norris 	/* Account for Hamming ECC and for BCH 512B vs 1KiB sectors */
2589decba6d4SFlorian Fainelli 	if (is_hamming_ecc(host->ctrl, cfg))
259027c5b17cSBrian Norris 		sprintf(buf, ", Hamming ECC");
259127c5b17cSBrian Norris 	else if (cfg->sector_size_1k)
259227c5b17cSBrian Norris 		sprintf(buf, ", BCH-%u (1KiB sector)", cfg->ecc_level << 1);
259327c5b17cSBrian Norris 	else
259480204124SHauke Mehrtens 		sprintf(buf, ", BCH-%u", cfg->ecc_level);
259527c5b17cSBrian Norris }
259627c5b17cSBrian Norris 
259727c5b17cSBrian Norris /*
259827c5b17cSBrian Norris  * Minimum number of bytes to address a page. Calculated as:
259927c5b17cSBrian Norris  *     roundup(log2(size / page-size) / 8)
260027c5b17cSBrian Norris  *
260127c5b17cSBrian Norris  * NB: the following does not "round up" for non-power-of-2 'size'; but this is
260227c5b17cSBrian Norris  *     OK because many other things will break if 'size' is irregular...
260327c5b17cSBrian Norris  */
260427c5b17cSBrian Norris static inline int get_blk_adr_bytes(u64 size, u32 writesize)
260527c5b17cSBrian Norris {
260627c5b17cSBrian Norris 	return ALIGN(ilog2(size) - ilog2(writesize), 8) >> 3;
260727c5b17cSBrian Norris }
260827c5b17cSBrian Norris 
260927c5b17cSBrian Norris static int brcmnand_setup_dev(struct brcmnand_host *host)
261027c5b17cSBrian Norris {
2611f1c4c999SBoris BREZILLON 	struct mtd_info *mtd = nand_to_mtd(&host->chip);
261227c5b17cSBrian Norris 	struct nand_chip *chip = &host->chip;
261353576c7bSMiquel Raynal 	const struct nand_ecc_props *requirements =
261453576c7bSMiquel Raynal 		nanddev_get_ecc_requirements(&chip->base);
261527c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
261627c5b17cSBrian Norris 	struct brcmnand_cfg *cfg = &host->hwcfg;
261727c5b17cSBrian Norris 	char msg[128];
261827c5b17cSBrian Norris 	u32 offs, tmp, oob_sector;
261927c5b17cSBrian Norris 	int ret;
262027c5b17cSBrian Norris 
262127c5b17cSBrian Norris 	memset(cfg, 0, sizeof(*cfg));
262227c5b17cSBrian Norris 
262344ec23c9SBoris BREZILLON 	ret = of_property_read_u32(nand_get_flash_node(chip),
262461528d88SMarek Vasut 				   "brcm,nand-oob-sector-size",
262527c5b17cSBrian Norris 				   &oob_sector);
262627c5b17cSBrian Norris 	if (ret) {
262727c5b17cSBrian Norris 		/* Use detected size */
262827c5b17cSBrian Norris 		cfg->spare_area_size = mtd->oobsize /
262927c5b17cSBrian Norris 					(mtd->writesize >> FC_SHIFT);
263027c5b17cSBrian Norris 	} else {
263127c5b17cSBrian Norris 		cfg->spare_area_size = oob_sector;
263227c5b17cSBrian Norris 	}
263327c5b17cSBrian Norris 	if (cfg->spare_area_size > ctrl->max_oob)
263427c5b17cSBrian Norris 		cfg->spare_area_size = ctrl->max_oob;
263527c5b17cSBrian Norris 	/*
263627c5b17cSBrian Norris 	 * Set oobsize to be consistent with controller's spare_area_size, as
263727c5b17cSBrian Norris 	 * the rest is inaccessible.
263827c5b17cSBrian Norris 	 */
263927c5b17cSBrian Norris 	mtd->oobsize = cfg->spare_area_size * (mtd->writesize >> FC_SHIFT);
264027c5b17cSBrian Norris 
264127c5b17cSBrian Norris 	cfg->device_size = mtd->size;
264227c5b17cSBrian Norris 	cfg->block_size = mtd->erasesize;
264327c5b17cSBrian Norris 	cfg->page_size = mtd->writesize;
264427c5b17cSBrian Norris 	cfg->device_width = (chip->options & NAND_BUSWIDTH_16) ? 16 : 8;
264527c5b17cSBrian Norris 	cfg->col_adr_bytes = 2;
264627c5b17cSBrian Norris 	cfg->blk_adr_bytes = get_blk_adr_bytes(mtd->size, mtd->writesize);
264727c5b17cSBrian Norris 
2648bace41f8SMiquel Raynal 	if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) {
2649666b6568SBrian Norris 		dev_err(ctrl->dev, "only HW ECC supported; selected: %d\n",
2650bace41f8SMiquel Raynal 			chip->ecc.engine_type);
2651666b6568SBrian Norris 		return -EINVAL;
2652666b6568SBrian Norris 	}
2653666b6568SBrian Norris 
2654e0a564aeSMiquel Raynal 	if (chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) {
2655666b6568SBrian Norris 		if (chip->ecc.strength == 1 && chip->ecc.size == 512)
2656666b6568SBrian Norris 			/* Default to Hamming for 1-bit ECC, if unspecified */
2657e0a564aeSMiquel Raynal 			chip->ecc.algo = NAND_ECC_ALGO_HAMMING;
2658666b6568SBrian Norris 		else
2659666b6568SBrian Norris 			/* Otherwise, BCH */
2660e0a564aeSMiquel Raynal 			chip->ecc.algo = NAND_ECC_ALGO_BCH;
2661666b6568SBrian Norris 	}
2662666b6568SBrian Norris 
2663e0a564aeSMiquel Raynal 	if (chip->ecc.algo == NAND_ECC_ALGO_HAMMING &&
2664e0a564aeSMiquel Raynal 	    (chip->ecc.strength != 1 || chip->ecc.size != 512)) {
2665666b6568SBrian Norris 		dev_err(ctrl->dev, "invalid Hamming params: %d bits per %d bytes\n",
2666666b6568SBrian Norris 			chip->ecc.strength, chip->ecc.size);
2667666b6568SBrian Norris 		return -EINVAL;
2668666b6568SBrian Norris 	}
2669666b6568SBrian Norris 
2670bace41f8SMiquel Raynal 	if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_NONE &&
267178933218SKamal Dasu 	    (!chip->ecc.size || !chip->ecc.strength)) {
267253576c7bSMiquel Raynal 		if (requirements->step_size && requirements->strength) {
267378933218SKamal Dasu 			/* use detected ECC parameters */
267453576c7bSMiquel Raynal 			chip->ecc.size = requirements->step_size;
267553576c7bSMiquel Raynal 			chip->ecc.strength = requirements->strength;
267678933218SKamal Dasu 			dev_info(ctrl->dev, "Using ECC step-size %d, strength %d\n",
267778933218SKamal Dasu 				chip->ecc.size, chip->ecc.strength);
267878933218SKamal Dasu 		}
267978933218SKamal Dasu 	}
268078933218SKamal Dasu 
268127c5b17cSBrian Norris 	switch (chip->ecc.size) {
268227c5b17cSBrian Norris 	case 512:
2683e0a564aeSMiquel Raynal 		if (chip->ecc.algo == NAND_ECC_ALGO_HAMMING)
268427c5b17cSBrian Norris 			cfg->ecc_level = 15;
268527c5b17cSBrian Norris 		else
268627c5b17cSBrian Norris 			cfg->ecc_level = chip->ecc.strength;
268727c5b17cSBrian Norris 		cfg->sector_size_1k = 0;
268827c5b17cSBrian Norris 		break;
268927c5b17cSBrian Norris 	case 1024:
269027c5b17cSBrian Norris 		if (!(ctrl->features & BRCMNAND_HAS_1K_SECTORS)) {
269127c5b17cSBrian Norris 			dev_err(ctrl->dev, "1KB sectors not supported\n");
269227c5b17cSBrian Norris 			return -EINVAL;
269327c5b17cSBrian Norris 		}
269427c5b17cSBrian Norris 		if (chip->ecc.strength & 0x1) {
269527c5b17cSBrian Norris 			dev_err(ctrl->dev,
269627c5b17cSBrian Norris 				"odd ECC not supported with 1KB sectors\n");
269727c5b17cSBrian Norris 			return -EINVAL;
269827c5b17cSBrian Norris 		}
269927c5b17cSBrian Norris 
270027c5b17cSBrian Norris 		cfg->ecc_level = chip->ecc.strength >> 1;
270127c5b17cSBrian Norris 		cfg->sector_size_1k = 1;
270227c5b17cSBrian Norris 		break;
270327c5b17cSBrian Norris 	default:
270427c5b17cSBrian Norris 		dev_err(ctrl->dev, "unsupported ECC size: %d\n",
270527c5b17cSBrian Norris 			chip->ecc.size);
270627c5b17cSBrian Norris 		return -EINVAL;
270727c5b17cSBrian Norris 	}
270827c5b17cSBrian Norris 
270927c5b17cSBrian Norris 	cfg->ful_adr_bytes = cfg->blk_adr_bytes;
271027c5b17cSBrian Norris 	if (mtd->writesize > 512)
271127c5b17cSBrian Norris 		cfg->ful_adr_bytes += cfg->col_adr_bytes;
271227c5b17cSBrian Norris 	else
271327c5b17cSBrian Norris 		cfg->ful_adr_bytes += 1;
271427c5b17cSBrian Norris 
271527c5b17cSBrian Norris 	ret = brcmnand_set_cfg(host, cfg);
271627c5b17cSBrian Norris 	if (ret)
271727c5b17cSBrian Norris 		return ret;
271827c5b17cSBrian Norris 
271927c5b17cSBrian Norris 	brcmnand_set_ecc_enabled(host, 1);
272027c5b17cSBrian Norris 
2721decba6d4SFlorian Fainelli 	brcmnand_print_cfg(host, msg, cfg);
272227c5b17cSBrian Norris 	dev_info(ctrl->dev, "detected %s\n", msg);
272327c5b17cSBrian Norris 
272427c5b17cSBrian Norris 	/* Configure ACC_CONTROL */
272527c5b17cSBrian Norris 	offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_ACC_CONTROL);
272627c5b17cSBrian Norris 	tmp = nand_readreg(ctrl, offs);
272727c5b17cSBrian Norris 	tmp &= ~ACC_CONTROL_PARTIAL_PAGE;
272827c5b17cSBrian Norris 	tmp &= ~ACC_CONTROL_RD_ERASED;
2729decba6d4SFlorian Fainelli 
2730decba6d4SFlorian Fainelli 	/* We need to turn on Read from erased paged protected by ECC */
2731decba6d4SFlorian Fainelli 	if (ctrl->nand_version >= 0x0702)
2732decba6d4SFlorian Fainelli 		tmp |= ACC_CONTROL_RD_ERASED;
273327c5b17cSBrian Norris 	tmp &= ~ACC_CONTROL_FAST_PGM_RDIN;
2734f953f0f8SKamal Dasu 	if (ctrl->features & BRCMNAND_HAS_PREFETCH)
273527c5b17cSBrian Norris 		tmp &= ~ACC_CONTROL_PREFETCH;
2736f953f0f8SKamal Dasu 
273727c5b17cSBrian Norris 	nand_writereg(ctrl, offs, tmp);
273827c5b17cSBrian Norris 
273927c5b17cSBrian Norris 	return 0;
274027c5b17cSBrian Norris }
274127c5b17cSBrian Norris 
27424918b905SMiquel Raynal static int brcmnand_attach_chip(struct nand_chip *chip)
27434918b905SMiquel Raynal {
27444918b905SMiquel Raynal 	struct mtd_info *mtd = nand_to_mtd(chip);
27454918b905SMiquel Raynal 	struct brcmnand_host *host = nand_get_controller_data(chip);
27464918b905SMiquel Raynal 	int ret;
27474918b905SMiquel Raynal 
27484918b905SMiquel Raynal 	chip->options |= NAND_NO_SUBPAGE_WRITE;
27494918b905SMiquel Raynal 	/*
27504918b905SMiquel Raynal 	 * Avoid (for instance) kmap()'d buffers from JFFS2, which we can't DMA
27514918b905SMiquel Raynal 	 * to/from, and have nand_base pass us a bounce buffer instead, as
27524918b905SMiquel Raynal 	 * needed.
27534918b905SMiquel Raynal 	 */
2754ce8148d7SMiquel Raynal 	chip->options |= NAND_USES_DMA;
27554918b905SMiquel Raynal 
27564918b905SMiquel Raynal 	if (chip->bbt_options & NAND_BBT_USE_FLASH)
27574918b905SMiquel Raynal 		chip->bbt_options |= NAND_BBT_NO_OOB;
27584918b905SMiquel Raynal 
27594918b905SMiquel Raynal 	if (brcmnand_setup_dev(host))
27604918b905SMiquel Raynal 		return -ENXIO;
27614918b905SMiquel Raynal 
27624918b905SMiquel Raynal 	chip->ecc.size = host->hwcfg.sector_size_1k ? 1024 : 512;
27634918b905SMiquel Raynal 
27644918b905SMiquel Raynal 	/* only use our internal HW threshold */
27654918b905SMiquel Raynal 	mtd->bitflip_threshold = 1;
27664918b905SMiquel Raynal 
27674918b905SMiquel Raynal 	ret = brcmstb_choose_ecc_layout(host);
27684918b905SMiquel Raynal 
2769f5200c14SÁlvaro Fernández Rojas 	/* If OOB is written with ECC enabled it will cause ECC errors */
2770f5200c14SÁlvaro Fernández Rojas 	if (is_hamming_ecc(host->ctrl, &host->hwcfg)) {
2771f5200c14SÁlvaro Fernández Rojas 		chip->ecc.write_oob = brcmnand_write_oob_raw;
2772f5200c14SÁlvaro Fernández Rojas 		chip->ecc.read_oob = brcmnand_read_oob_raw;
2773f5200c14SÁlvaro Fernández Rojas 	}
2774f5200c14SÁlvaro Fernández Rojas 
27754918b905SMiquel Raynal 	return ret;
27764918b905SMiquel Raynal }
27774918b905SMiquel Raynal 
27784918b905SMiquel Raynal static const struct nand_controller_ops brcmnand_controller_ops = {
27794918b905SMiquel Raynal 	.attach_chip = brcmnand_attach_chip,
27804918b905SMiquel Raynal };
27814918b905SMiquel Raynal 
27828e591300SFlorian Fainelli static int brcmnand_init_cs(struct brcmnand_host *host,
27838e591300SFlorian Fainelli 			    const char * const *part_probe_types)
278427c5b17cSBrian Norris {
278527c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
2786c0d08a14SFlorian Fainelli 	struct device *dev = ctrl->dev;
278727c5b17cSBrian Norris 	struct mtd_info *mtd;
278827c5b17cSBrian Norris 	struct nand_chip *chip;
27895e65d48bSBrian Norris 	int ret;
27904d1ea982SAnup Patel 	u16 cfg_offs;
279127c5b17cSBrian Norris 
2792f1c4c999SBoris BREZILLON 	mtd = nand_to_mtd(&host->chip);
279327c5b17cSBrian Norris 	chip = &host->chip;
279427c5b17cSBrian Norris 
2795d699ed25SBoris BREZILLON 	nand_set_controller_data(chip, host);
2796c0d08a14SFlorian Fainelli 	mtd->name = devm_kasprintf(dev, GFP_KERNEL, "brcmnand.%d",
279727c5b17cSBrian Norris 				   host->cs);
2798039b4377SFabio Estevam 	if (!mtd->name)
2799039b4377SFabio Estevam 		return -ENOMEM;
2800039b4377SFabio Estevam 
280127c5b17cSBrian Norris 	mtd->owner = THIS_MODULE;
2802c0d08a14SFlorian Fainelli 	mtd->dev.parent = dev;
280327c5b17cSBrian Norris 
2804bf6065c6SBoris Brezillon 	chip->legacy.cmd_ctrl = brcmnand_cmd_ctrl;
2805bf6065c6SBoris Brezillon 	chip->legacy.cmdfunc = brcmnand_cmdfunc;
28068395b753SBoris Brezillon 	chip->legacy.waitfunc = brcmnand_waitfunc;
2807716bbbabSBoris Brezillon 	chip->legacy.read_byte = brcmnand_read_byte;
2808716bbbabSBoris Brezillon 	chip->legacy.read_buf = brcmnand_read_buf;
2809716bbbabSBoris Brezillon 	chip->legacy.write_buf = brcmnand_write_buf;
281027c5b17cSBrian Norris 
2811bace41f8SMiquel Raynal 	chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
281227c5b17cSBrian Norris 	chip->ecc.read_page = brcmnand_read_page;
281327c5b17cSBrian Norris 	chip->ecc.write_page = brcmnand_write_page;
281427c5b17cSBrian Norris 	chip->ecc.read_page_raw = brcmnand_read_page_raw;
281527c5b17cSBrian Norris 	chip->ecc.write_page_raw = brcmnand_write_page_raw;
281627c5b17cSBrian Norris 	chip->ecc.write_oob_raw = brcmnand_write_oob_raw;
281727c5b17cSBrian Norris 	chip->ecc.read_oob_raw = brcmnand_read_oob_raw;
281827c5b17cSBrian Norris 	chip->ecc.read_oob = brcmnand_read_oob;
281927c5b17cSBrian Norris 	chip->ecc.write_oob = brcmnand_write_oob;
282027c5b17cSBrian Norris 
282127c5b17cSBrian Norris 	chip->controller = &ctrl->controller;
282227c5b17cSBrian Norris 
28234d1ea982SAnup Patel 	/*
28244d1ea982SAnup Patel 	 * The bootloader might have configured 16bit mode but
28254d1ea982SAnup Patel 	 * NAND READID command only works in 8bit mode. We force
28264d1ea982SAnup Patel 	 * 8bit mode here to ensure that NAND READID commands works.
28274d1ea982SAnup Patel 	 */
28284d1ea982SAnup Patel 	cfg_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_CFG);
28294d1ea982SAnup Patel 	nand_writereg(ctrl, cfg_offs,
28304d1ea982SAnup Patel 		      nand_readreg(ctrl, cfg_offs) & ~CFG_BUS_WIDTH);
28314d1ea982SAnup Patel 
283200ad378fSBoris Brezillon 	ret = nand_scan(chip, 1);
2833c25cca03SMasahiro Yamada 	if (ret)
2834c25cca03SMasahiro Yamada 		return ret;
283527c5b17cSBrian Norris 
28368e591300SFlorian Fainelli 	ret = mtd_device_parse_register(mtd, part_probe_types, NULL, NULL, 0);
28375826b880SMiquel Raynal 	if (ret)
28385826b880SMiquel Raynal 		nand_cleanup(chip);
28395826b880SMiquel Raynal 
28405826b880SMiquel Raynal 	return ret;
284127c5b17cSBrian Norris }
284227c5b17cSBrian Norris 
284327c5b17cSBrian Norris static void brcmnand_save_restore_cs_config(struct brcmnand_host *host,
284427c5b17cSBrian Norris 					    int restore)
284527c5b17cSBrian Norris {
284627c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = host->ctrl;
284727c5b17cSBrian Norris 	u16 cfg_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_CFG);
284827c5b17cSBrian Norris 	u16 cfg_ext_offs = brcmnand_cs_offset(ctrl, host->cs,
284927c5b17cSBrian Norris 			BRCMNAND_CS_CFG_EXT);
285027c5b17cSBrian Norris 	u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs,
285127c5b17cSBrian Norris 			BRCMNAND_CS_ACC_CONTROL);
285227c5b17cSBrian Norris 	u16 t1_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_TIMING1);
285327c5b17cSBrian Norris 	u16 t2_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_TIMING2);
285427c5b17cSBrian Norris 
285527c5b17cSBrian Norris 	if (restore) {
285627c5b17cSBrian Norris 		nand_writereg(ctrl, cfg_offs, host->hwcfg.config);
285727c5b17cSBrian Norris 		if (cfg_offs != cfg_ext_offs)
285827c5b17cSBrian Norris 			nand_writereg(ctrl, cfg_ext_offs,
285927c5b17cSBrian Norris 				      host->hwcfg.config_ext);
286027c5b17cSBrian Norris 		nand_writereg(ctrl, acc_control_offs, host->hwcfg.acc_control);
286127c5b17cSBrian Norris 		nand_writereg(ctrl, t1_offs, host->hwcfg.timing_1);
286227c5b17cSBrian Norris 		nand_writereg(ctrl, t2_offs, host->hwcfg.timing_2);
286327c5b17cSBrian Norris 	} else {
286427c5b17cSBrian Norris 		host->hwcfg.config = nand_readreg(ctrl, cfg_offs);
286527c5b17cSBrian Norris 		if (cfg_offs != cfg_ext_offs)
286627c5b17cSBrian Norris 			host->hwcfg.config_ext =
286727c5b17cSBrian Norris 				nand_readreg(ctrl, cfg_ext_offs);
286827c5b17cSBrian Norris 		host->hwcfg.acc_control = nand_readreg(ctrl, acc_control_offs);
286927c5b17cSBrian Norris 		host->hwcfg.timing_1 = nand_readreg(ctrl, t1_offs);
287027c5b17cSBrian Norris 		host->hwcfg.timing_2 = nand_readreg(ctrl, t2_offs);
287127c5b17cSBrian Norris 	}
287227c5b17cSBrian Norris }
287327c5b17cSBrian Norris 
287427c5b17cSBrian Norris static int brcmnand_suspend(struct device *dev)
287527c5b17cSBrian Norris {
287627c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = dev_get_drvdata(dev);
287727c5b17cSBrian Norris 	struct brcmnand_host *host;
287827c5b17cSBrian Norris 
287927c5b17cSBrian Norris 	list_for_each_entry(host, &ctrl->host_list, node)
288027c5b17cSBrian Norris 		brcmnand_save_restore_cs_config(host, 0);
288127c5b17cSBrian Norris 
288227c5b17cSBrian Norris 	ctrl->nand_cs_nand_select = brcmnand_read_reg(ctrl, BRCMNAND_CS_SELECT);
288327c5b17cSBrian Norris 	ctrl->nand_cs_nand_xor = brcmnand_read_reg(ctrl, BRCMNAND_CS_XOR);
288427c5b17cSBrian Norris 	ctrl->corr_stat_threshold =
288527c5b17cSBrian Norris 		brcmnand_read_reg(ctrl, BRCMNAND_CORR_THRESHOLD);
288627c5b17cSBrian Norris 
288727c5b17cSBrian Norris 	if (has_flash_dma(ctrl))
288827c5b17cSBrian Norris 		ctrl->flash_dma_mode = flash_dma_readl(ctrl, FLASH_DMA_MODE);
2889a5d53ad2SKamal Dasu 	else if (has_edu(ctrl))
2890a5d53ad2SKamal Dasu 		ctrl->edu_config = edu_readl(ctrl, EDU_CONFIG);
289127c5b17cSBrian Norris 
289227c5b17cSBrian Norris 	return 0;
289327c5b17cSBrian Norris }
289427c5b17cSBrian Norris 
289527c5b17cSBrian Norris static int brcmnand_resume(struct device *dev)
289627c5b17cSBrian Norris {
289727c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = dev_get_drvdata(dev);
289827c5b17cSBrian Norris 	struct brcmnand_host *host;
289927c5b17cSBrian Norris 
290027c5b17cSBrian Norris 	if (has_flash_dma(ctrl)) {
290127c5b17cSBrian Norris 		flash_dma_writel(ctrl, FLASH_DMA_MODE, ctrl->flash_dma_mode);
290227c5b17cSBrian Norris 		flash_dma_writel(ctrl, FLASH_DMA_ERROR_STATUS, 0);
290327c5b17cSBrian Norris 	}
290427c5b17cSBrian Norris 
2905f3a6a6c5SKamal Dasu 	if (has_edu(ctrl)) {
2906a5d53ad2SKamal Dasu 		ctrl->edu_config = edu_readl(ctrl, EDU_CONFIG);
2907a5d53ad2SKamal Dasu 		edu_writel(ctrl, EDU_CONFIG, ctrl->edu_config);
2908a5d53ad2SKamal Dasu 		edu_readl(ctrl, EDU_CONFIG);
2909a5d53ad2SKamal Dasu 		brcmnand_edu_init(ctrl);
2910a5d53ad2SKamal Dasu 	}
2911a5d53ad2SKamal Dasu 
291227c5b17cSBrian Norris 	brcmnand_write_reg(ctrl, BRCMNAND_CS_SELECT, ctrl->nand_cs_nand_select);
291327c5b17cSBrian Norris 	brcmnand_write_reg(ctrl, BRCMNAND_CS_XOR, ctrl->nand_cs_nand_xor);
291427c5b17cSBrian Norris 	brcmnand_write_reg(ctrl, BRCMNAND_CORR_THRESHOLD,
291527c5b17cSBrian Norris 			ctrl->corr_stat_threshold);
2916c26211d3SBrian Norris 	if (ctrl->soc) {
2917c26211d3SBrian Norris 		/* Clear/re-enable interrupt */
2918c26211d3SBrian Norris 		ctrl->soc->ctlrdy_ack(ctrl->soc);
2919c26211d3SBrian Norris 		ctrl->soc->ctlrdy_set_enabled(ctrl->soc, true);
2920c26211d3SBrian Norris 	}
292127c5b17cSBrian Norris 
292227c5b17cSBrian Norris 	list_for_each_entry(host, &ctrl->host_list, node) {
2923f1c4c999SBoris BREZILLON 		struct nand_chip *chip = &host->chip;
292427c5b17cSBrian Norris 
292527c5b17cSBrian Norris 		brcmnand_save_restore_cs_config(host, 1);
292627c5b17cSBrian Norris 
292727c5b17cSBrian Norris 		/* Reset the chip, required by some chips after power-up */
292897d90da8SBoris Brezillon 		nand_reset_op(chip);
292927c5b17cSBrian Norris 	}
293027c5b17cSBrian Norris 
293127c5b17cSBrian Norris 	return 0;
293227c5b17cSBrian Norris }
293327c5b17cSBrian Norris 
293427c5b17cSBrian Norris const struct dev_pm_ops brcmnand_pm_ops = {
293527c5b17cSBrian Norris 	.suspend		= brcmnand_suspend,
293627c5b17cSBrian Norris 	.resume			= brcmnand_resume,
293727c5b17cSBrian Norris };
293827c5b17cSBrian Norris EXPORT_SYMBOL_GPL(brcmnand_pm_ops);
293927c5b17cSBrian Norris 
2940e02dacd3SMiquel Raynal static const struct of_device_id __maybe_unused brcmnand_of_match[] = {
29417e7c7df5SÁlvaro Fernández Rojas 	{ .compatible = "brcm,brcmnand-v2.1" },
29427e7c7df5SÁlvaro Fernández Rojas 	{ .compatible = "brcm,brcmnand-v2.2" },
294327c5b17cSBrian Norris 	{ .compatible = "brcm,brcmnand-v4.0" },
294427c5b17cSBrian Norris 	{ .compatible = "brcm,brcmnand-v5.0" },
294527c5b17cSBrian Norris 	{ .compatible = "brcm,brcmnand-v6.0" },
294627c5b17cSBrian Norris 	{ .compatible = "brcm,brcmnand-v6.1" },
2947269ecf03SFlorian Fainelli 	{ .compatible = "brcm,brcmnand-v6.2" },
294827c5b17cSBrian Norris 	{ .compatible = "brcm,brcmnand-v7.0" },
294927c5b17cSBrian Norris 	{ .compatible = "brcm,brcmnand-v7.1" },
2950decba6d4SFlorian Fainelli 	{ .compatible = "brcm,brcmnand-v7.2" },
29510c06da57SKamal Dasu 	{ .compatible = "brcm,brcmnand-v7.3" },
295227c5b17cSBrian Norris 	{},
295327c5b17cSBrian Norris };
295427c5b17cSBrian Norris MODULE_DEVICE_TABLE(of, brcmnand_of_match);
295527c5b17cSBrian Norris 
295627c5b17cSBrian Norris /***********************************************************************
295727c5b17cSBrian Norris  * Platform driver setup (per controller)
295827c5b17cSBrian Norris  ***********************************************************************/
2959a5d53ad2SKamal Dasu static int brcmnand_edu_setup(struct platform_device *pdev)
2960a5d53ad2SKamal Dasu {
2961a5d53ad2SKamal Dasu 	struct device *dev = &pdev->dev;
2962a5d53ad2SKamal Dasu 	struct brcmnand_controller *ctrl = dev_get_drvdata(&pdev->dev);
2963a5d53ad2SKamal Dasu 	struct resource *res;
2964a5d53ad2SKamal Dasu 	int ret;
2965a5d53ad2SKamal Dasu 
2966a5d53ad2SKamal Dasu 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "flash-edu");
2967a5d53ad2SKamal Dasu 	if (res) {
2968a5d53ad2SKamal Dasu 		ctrl->edu_base = devm_ioremap_resource(dev, res);
2969a5d53ad2SKamal Dasu 		if (IS_ERR(ctrl->edu_base))
2970a5d53ad2SKamal Dasu 			return PTR_ERR(ctrl->edu_base);
2971a5d53ad2SKamal Dasu 
2972a5d53ad2SKamal Dasu 		ctrl->edu_offsets = edu_regs;
2973a5d53ad2SKamal Dasu 
2974a5d53ad2SKamal Dasu 		edu_writel(ctrl, EDU_CONFIG, EDU_CONFIG_MODE_NAND |
2975a5d53ad2SKamal Dasu 			   EDU_CONFIG_SWAP_CFG);
2976a5d53ad2SKamal Dasu 		edu_readl(ctrl, EDU_CONFIG);
2977a5d53ad2SKamal Dasu 
2978a5d53ad2SKamal Dasu 		/* initialize edu */
2979a5d53ad2SKamal Dasu 		brcmnand_edu_init(ctrl);
2980a5d53ad2SKamal Dasu 
2981a5d53ad2SKamal Dasu 		ctrl->edu_irq = platform_get_irq_optional(pdev, 1);
2982a5d53ad2SKamal Dasu 		if (ctrl->edu_irq < 0) {
2983a5d53ad2SKamal Dasu 			dev_warn(dev,
2984a5d53ad2SKamal Dasu 				 "FLASH EDU enabled, using ctlrdy irq\n");
2985a5d53ad2SKamal Dasu 		} else {
2986a5d53ad2SKamal Dasu 			ret = devm_request_irq(dev, ctrl->edu_irq,
2987a5d53ad2SKamal Dasu 					       brcmnand_edu_irq, 0,
2988a5d53ad2SKamal Dasu 					       "brcmnand-edu", ctrl);
2989a5d53ad2SKamal Dasu 			if (ret < 0) {
2990a5d53ad2SKamal Dasu 				dev_err(ctrl->dev, "can't allocate IRQ %d: error %d\n",
2991a5d53ad2SKamal Dasu 					ctrl->edu_irq, ret);
2992a5d53ad2SKamal Dasu 				return ret;
2993a5d53ad2SKamal Dasu 			}
2994a5d53ad2SKamal Dasu 
2995a5d53ad2SKamal Dasu 			dev_info(dev, "FLASH EDU enabled using irq %u\n",
2996a5d53ad2SKamal Dasu 				 ctrl->edu_irq);
2997a5d53ad2SKamal Dasu 		}
2998a5d53ad2SKamal Dasu 	}
2999a5d53ad2SKamal Dasu 
3000a5d53ad2SKamal Dasu 	return 0;
3001a5d53ad2SKamal Dasu }
300227c5b17cSBrian Norris 
300327c5b17cSBrian Norris int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
300427c5b17cSBrian Norris {
30058e591300SFlorian Fainelli 	struct brcmnand_platform_data *pd = dev_get_platdata(&pdev->dev);
300627c5b17cSBrian Norris 	struct device *dev = &pdev->dev;
300727c5b17cSBrian Norris 	struct device_node *dn = dev->of_node, *child;
3008bcb83a19SHauke Mehrtens 	struct brcmnand_controller *ctrl;
30098e591300SFlorian Fainelli 	struct brcmnand_host *host;
301027c5b17cSBrian Norris 	struct resource *res;
301127c5b17cSBrian Norris 	int ret;
301227c5b17cSBrian Norris 
30138e591300SFlorian Fainelli 	if (dn && !of_match_node(brcmnand_of_match, dn))
301427c5b17cSBrian Norris 		return -ENODEV;
301527c5b17cSBrian Norris 
301627c5b17cSBrian Norris 	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
301727c5b17cSBrian Norris 	if (!ctrl)
301827c5b17cSBrian Norris 		return -ENOMEM;
301927c5b17cSBrian Norris 
302027c5b17cSBrian Norris 	dev_set_drvdata(dev, ctrl);
302127c5b17cSBrian Norris 	ctrl->dev = dev;
30229e37532bSFlorian Fainelli 	ctrl->soc = soc;
302327c5b17cSBrian Norris 
302425f97138SFlorian Fainelli 	/* Enable the static key if the soc provides I/O operations indicating
302525f97138SFlorian Fainelli 	 * that a non-memory mapped IO access path must be used
302625f97138SFlorian Fainelli 	 */
302725f97138SFlorian Fainelli 	if (brcmnand_soc_has_ops(ctrl->soc))
302825f97138SFlorian Fainelli 		static_branch_enable(&brcmnand_soc_has_ops_key);
302927c5b17cSBrian Norris 
303027c5b17cSBrian Norris 	init_completion(&ctrl->done);
303127c5b17cSBrian Norris 	init_completion(&ctrl->dma_done);
3032a5d53ad2SKamal Dasu 	init_completion(&ctrl->edu_done);
30337da45139SMiquel Raynal 	nand_controller_init(&ctrl->controller);
30344918b905SMiquel Raynal 	ctrl->controller.ops = &brcmnand_controller_ops;
303527c5b17cSBrian Norris 	INIT_LIST_HEAD(&ctrl->host_list);
303627c5b17cSBrian Norris 
303727c5b17cSBrian Norris 	/* NAND register range */
303827c5b17cSBrian Norris 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
303927c5b17cSBrian Norris 	ctrl->nand_base = devm_ioremap_resource(dev, res);
30408e591300SFlorian Fainelli 	if (IS_ERR(ctrl->nand_base) && !brcmnand_soc_has_ops(soc))
304127c5b17cSBrian Norris 		return PTR_ERR(ctrl->nand_base);
304227c5b17cSBrian Norris 
30435c05bc00SSimon Arlott 	/* Enable clock before using NAND registers */
30445c05bc00SSimon Arlott 	ctrl->clk = devm_clk_get(dev, "nand");
30455c05bc00SSimon Arlott 	if (!IS_ERR(ctrl->clk)) {
30465c05bc00SSimon Arlott 		ret = clk_prepare_enable(ctrl->clk);
30475c05bc00SSimon Arlott 		if (ret)
30485c05bc00SSimon Arlott 			return ret;
30495c05bc00SSimon Arlott 	} else {
30505c05bc00SSimon Arlott 		ret = PTR_ERR(ctrl->clk);
30515c05bc00SSimon Arlott 		if (ret == -EPROBE_DEFER)
30525c05bc00SSimon Arlott 			return ret;
30535c05bc00SSimon Arlott 
30545c05bc00SSimon Arlott 		ctrl->clk = NULL;
30555c05bc00SSimon Arlott 	}
30565c05bc00SSimon Arlott 
305727c5b17cSBrian Norris 	/* Initialize NAND revision */
305827c5b17cSBrian Norris 	ret = brcmnand_revision_init(ctrl);
305927c5b17cSBrian Norris 	if (ret)
30605c05bc00SSimon Arlott 		goto err;
306127c5b17cSBrian Norris 
306227c5b17cSBrian Norris 	/*
306327c5b17cSBrian Norris 	 * Most chips have this cache at a fixed offset within 'nand' block.
306427c5b17cSBrian Norris 	 * Some must specify this region separately.
306527c5b17cSBrian Norris 	 */
306627c5b17cSBrian Norris 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand-cache");
306727c5b17cSBrian Norris 	if (res) {
306827c5b17cSBrian Norris 		ctrl->nand_fc = devm_ioremap_resource(dev, res);
30695c05bc00SSimon Arlott 		if (IS_ERR(ctrl->nand_fc)) {
30705c05bc00SSimon Arlott 			ret = PTR_ERR(ctrl->nand_fc);
30715c05bc00SSimon Arlott 			goto err;
30725c05bc00SSimon Arlott 		}
307327c5b17cSBrian Norris 	} else {
307427c5b17cSBrian Norris 		ctrl->nand_fc = ctrl->nand_base +
307527c5b17cSBrian Norris 				ctrl->reg_offsets[BRCMNAND_FC_BASE];
307627c5b17cSBrian Norris 	}
307727c5b17cSBrian Norris 
307827c5b17cSBrian Norris 	/* FLASH_DMA */
307927c5b17cSBrian Norris 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "flash-dma");
308027c5b17cSBrian Norris 	if (res) {
308127c5b17cSBrian Norris 		ctrl->flash_dma_base = devm_ioremap_resource(dev, res);
30825c05bc00SSimon Arlott 		if (IS_ERR(ctrl->flash_dma_base)) {
30835c05bc00SSimon Arlott 			ret = PTR_ERR(ctrl->flash_dma_base);
30845c05bc00SSimon Arlott 			goto err;
30855c05bc00SSimon Arlott 		}
308627c5b17cSBrian Norris 
30870c06da57SKamal Dasu 		/* initialize the dma version */
30880c06da57SKamal Dasu 		brcmnand_flash_dma_revision_init(ctrl);
30890c06da57SKamal Dasu 
3090393947e5SFlorian Fainelli 		ret = -EIO;
3091393947e5SFlorian Fainelli 		if (ctrl->nand_version >= 0x0700)
3092393947e5SFlorian Fainelli 			ret = dma_set_mask_and_coherent(&pdev->dev,
3093393947e5SFlorian Fainelli 							DMA_BIT_MASK(40));
3094393947e5SFlorian Fainelli 		if (ret)
3095393947e5SFlorian Fainelli 			ret = dma_set_mask_and_coherent(&pdev->dev,
3096393947e5SFlorian Fainelli 							DMA_BIT_MASK(32));
3097393947e5SFlorian Fainelli 		if (ret)
3098393947e5SFlorian Fainelli 			goto err;
3099393947e5SFlorian Fainelli 
31000c06da57SKamal Dasu 		/* linked-list and stop on error */
31010c06da57SKamal Dasu 		flash_dma_writel(ctrl, FLASH_DMA_MODE, FLASH_DMA_MODE_MASK);
310227c5b17cSBrian Norris 		flash_dma_writel(ctrl, FLASH_DMA_ERROR_STATUS, 0);
310327c5b17cSBrian Norris 
310427c5b17cSBrian Norris 		/* Allocate descriptor(s) */
310527c5b17cSBrian Norris 		ctrl->dma_desc = dmam_alloc_coherent(dev,
310627c5b17cSBrian Norris 						     sizeof(*ctrl->dma_desc),
310727c5b17cSBrian Norris 						     &ctrl->dma_pa, GFP_KERNEL);
31085c05bc00SSimon Arlott 		if (!ctrl->dma_desc) {
31095c05bc00SSimon Arlott 			ret = -ENOMEM;
31105c05bc00SSimon Arlott 			goto err;
31115c05bc00SSimon Arlott 		}
311227c5b17cSBrian Norris 
311327c5b17cSBrian Norris 		ctrl->dma_irq = platform_get_irq(pdev, 1);
311427c5b17cSBrian Norris 		if ((int)ctrl->dma_irq < 0) {
311527c5b17cSBrian Norris 			dev_err(dev, "missing FLASH_DMA IRQ\n");
31165c05bc00SSimon Arlott 			ret = -ENODEV;
31175c05bc00SSimon Arlott 			goto err;
311827c5b17cSBrian Norris 		}
311927c5b17cSBrian Norris 
312027c5b17cSBrian Norris 		ret = devm_request_irq(dev, ctrl->dma_irq,
312127c5b17cSBrian Norris 				brcmnand_dma_irq, 0, DRV_NAME,
312227c5b17cSBrian Norris 				ctrl);
312327c5b17cSBrian Norris 		if (ret < 0) {
312427c5b17cSBrian Norris 			dev_err(dev, "can't allocate IRQ %d: error %d\n",
312527c5b17cSBrian Norris 					ctrl->dma_irq, ret);
31265c05bc00SSimon Arlott 			goto err;
312727c5b17cSBrian Norris 		}
312827c5b17cSBrian Norris 
312927c5b17cSBrian Norris 		dev_info(dev, "enabling FLASH_DMA\n");
3130a5d53ad2SKamal Dasu 		/* set flash dma transfer function to call */
3131a5d53ad2SKamal Dasu 		ctrl->dma_trans = brcmnand_dma_trans;
3132a5d53ad2SKamal Dasu 	} else	{
3133a5d53ad2SKamal Dasu 		ret = brcmnand_edu_setup(pdev);
3134a5d53ad2SKamal Dasu 		if (ret < 0)
3135a5d53ad2SKamal Dasu 			goto err;
3136a5d53ad2SKamal Dasu 
3137bee3ab8bSKamal Dasu 		if (has_edu(ctrl))
3138a5d53ad2SKamal Dasu 			/* set edu transfer function to call */
3139a5d53ad2SKamal Dasu 			ctrl->dma_trans = brcmnand_edu_trans;
314027c5b17cSBrian Norris 	}
314127c5b17cSBrian Norris 
314227c5b17cSBrian Norris 	/* Disable automatic device ID config, direct addressing */
314327c5b17cSBrian Norris 	brcmnand_rmw_reg(ctrl, BRCMNAND_CS_SELECT,
314427c5b17cSBrian Norris 			 CS_SELECT_AUTO_DEVICE_ID_CFG | 0xff, 0, 0);
314527c5b17cSBrian Norris 	/* Disable XOR addressing */
314627c5b17cSBrian Norris 	brcmnand_rmw_reg(ctrl, BRCMNAND_CS_XOR, 0xff, 0, 0);
314727c5b17cSBrian Norris 
314827c5b17cSBrian Norris 	if (ctrl->features & BRCMNAND_HAS_WP) {
314927c5b17cSBrian Norris 		/* Permanently disable write protection */
315027c5b17cSBrian Norris 		if (wp_on == 2)
315127c5b17cSBrian Norris 			brcmnand_set_wp(ctrl, false);
315227c5b17cSBrian Norris 	} else {
315327c5b17cSBrian Norris 		wp_on = 0;
315427c5b17cSBrian Norris 	}
315527c5b17cSBrian Norris 
315627c5b17cSBrian Norris 	/* IRQ */
3157f5619f37SFlorian Fainelli 	ctrl->irq = platform_get_irq_optional(pdev, 0);
3158f5619f37SFlorian Fainelli 	if (ctrl->irq > 0) {
3159c26211d3SBrian Norris 		/*
3160c26211d3SBrian Norris 		 * Some SoCs integrate this controller (e.g., its interrupt bits) in
3161c26211d3SBrian Norris 		 * interesting ways
3162c26211d3SBrian Norris 		 */
3163c26211d3SBrian Norris 		if (soc) {
3164c26211d3SBrian Norris 			ret = devm_request_irq(dev, ctrl->irq, brcmnand_irq, 0,
3165c26211d3SBrian Norris 					       DRV_NAME, ctrl);
3166c26211d3SBrian Norris 
3167c26211d3SBrian Norris 			/* Enable interrupt */
3168c26211d3SBrian Norris 			ctrl->soc->ctlrdy_ack(ctrl->soc);
3169c26211d3SBrian Norris 			ctrl->soc->ctlrdy_set_enabled(ctrl->soc, true);
3170c26211d3SBrian Norris 		} else {
3171c26211d3SBrian Norris 			/* Use standard interrupt infrastructure */
317227c5b17cSBrian Norris 			ret = devm_request_irq(dev, ctrl->irq, brcmnand_ctlrdy_irq, 0,
317327c5b17cSBrian Norris 					       DRV_NAME, ctrl);
3174c26211d3SBrian Norris 		}
317527c5b17cSBrian Norris 		if (ret < 0) {
317627c5b17cSBrian Norris 			dev_err(dev, "can't allocate IRQ %d: error %d\n",
317727c5b17cSBrian Norris 				ctrl->irq, ret);
31785c05bc00SSimon Arlott 			goto err;
317927c5b17cSBrian Norris 		}
3180f5619f37SFlorian Fainelli 	}
318127c5b17cSBrian Norris 
318227c5b17cSBrian Norris 	for_each_available_child_of_node(dn, child) {
318327c5b17cSBrian Norris 		if (of_device_is_compatible(child, "brcm,nandcs")) {
318427c5b17cSBrian Norris 
318527c5b17cSBrian Norris 			host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
3186081976bcSJulia Lawall 			if (!host) {
3187081976bcSJulia Lawall 				of_node_put(child);
31885c05bc00SSimon Arlott 				ret = -ENOMEM;
31895c05bc00SSimon Arlott 				goto err;
3190081976bcSJulia Lawall 			}
319127c5b17cSBrian Norris 			host->pdev = pdev;
319227c5b17cSBrian Norris 			host->ctrl = ctrl;
319327c5b17cSBrian Norris 
319475ac9447SFlorian Fainelli 			ret = of_property_read_u32(child, "reg", &host->cs);
319575ac9447SFlorian Fainelli 			if (ret) {
319675ac9447SFlorian Fainelli 				dev_err(dev, "can't get chip-select\n");
319775ac9447SFlorian Fainelli 				devm_kfree(dev, host);
319875ac9447SFlorian Fainelli 				continue;
319975ac9447SFlorian Fainelli 			}
320075ac9447SFlorian Fainelli 
320175ac9447SFlorian Fainelli 			nand_set_flash_node(&host->chip, child);
320275ac9447SFlorian Fainelli 
32038e591300SFlorian Fainelli 			ret = brcmnand_init_cs(host, NULL);
3204081976bcSJulia Lawall 			if (ret) {
3205081976bcSJulia Lawall 				devm_kfree(dev, host);
320627c5b17cSBrian Norris 				continue; /* Try all chip-selects */
3207081976bcSJulia Lawall 			}
320827c5b17cSBrian Norris 
320927c5b17cSBrian Norris 			list_add_tail(&host->node, &ctrl->host_list);
321027c5b17cSBrian Norris 		}
321127c5b17cSBrian Norris 	}
321227c5b17cSBrian Norris 
32138e591300SFlorian Fainelli 	if (!list_empty(&ctrl->host_list))
32148e591300SFlorian Fainelli 		return 0;
32158e591300SFlorian Fainelli 
32168e591300SFlorian Fainelli 	if (!pd) {
32178e591300SFlorian Fainelli 		ret = -ENODEV;
32188e591300SFlorian Fainelli 		goto err;
32198e591300SFlorian Fainelli 	}
32208e591300SFlorian Fainelli 
32218e591300SFlorian Fainelli 	/* If we got there we must have been probing via platform data */
32228e591300SFlorian Fainelli 	host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
32238e591300SFlorian Fainelli 	if (!host) {
32248e591300SFlorian Fainelli 		ret = -ENOMEM;
32258e591300SFlorian Fainelli 		goto err;
32268e591300SFlorian Fainelli 	}
32278e591300SFlorian Fainelli 	host->pdev = pdev;
32288e591300SFlorian Fainelli 	host->ctrl = ctrl;
32298e591300SFlorian Fainelli 	host->cs = pd->chip_select;
32308e591300SFlorian Fainelli 	host->chip.ecc.size = pd->ecc_stepsize;
32318e591300SFlorian Fainelli 	host->chip.ecc.strength = pd->ecc_strength;
32328e591300SFlorian Fainelli 
32338e591300SFlorian Fainelli 	ret = brcmnand_init_cs(host, pd->part_probe_types);
32348e591300SFlorian Fainelli 	if (ret)
32358e591300SFlorian Fainelli 		goto err;
32368e591300SFlorian Fainelli 
32378e591300SFlorian Fainelli 	list_add_tail(&host->node, &ctrl->host_list);
32388e591300SFlorian Fainelli 
323927c5b17cSBrian Norris 	/* No chip-selects could initialize properly */
32405c05bc00SSimon Arlott 	if (list_empty(&ctrl->host_list)) {
32415c05bc00SSimon Arlott 		ret = -ENODEV;
32425c05bc00SSimon Arlott 		goto err;
32435c05bc00SSimon Arlott 	}
324427c5b17cSBrian Norris 
324527c5b17cSBrian Norris 	return 0;
32465c05bc00SSimon Arlott 
32475c05bc00SSimon Arlott err:
32485c05bc00SSimon Arlott 	clk_disable_unprepare(ctrl->clk);
32495c05bc00SSimon Arlott 	return ret;
32505c05bc00SSimon Arlott 
325127c5b17cSBrian Norris }
325227c5b17cSBrian Norris EXPORT_SYMBOL_GPL(brcmnand_probe);
325327c5b17cSBrian Norris 
325427c5b17cSBrian Norris int brcmnand_remove(struct platform_device *pdev)
325527c5b17cSBrian Norris {
325627c5b17cSBrian Norris 	struct brcmnand_controller *ctrl = dev_get_drvdata(&pdev->dev);
325727c5b17cSBrian Norris 	struct brcmnand_host *host;
3258937d039dSMiquel Raynal 	struct nand_chip *chip;
3259937d039dSMiquel Raynal 	int ret;
326027c5b17cSBrian Norris 
3261937d039dSMiquel Raynal 	list_for_each_entry(host, &ctrl->host_list, node) {
3262937d039dSMiquel Raynal 		chip = &host->chip;
3263937d039dSMiquel Raynal 		ret = mtd_device_unregister(nand_to_mtd(chip));
3264937d039dSMiquel Raynal 		WARN_ON(ret);
3265937d039dSMiquel Raynal 		nand_cleanup(chip);
3266937d039dSMiquel Raynal 	}
326727c5b17cSBrian Norris 
32685c05bc00SSimon Arlott 	clk_disable_unprepare(ctrl->clk);
32695c05bc00SSimon Arlott 
327027c5b17cSBrian Norris 	dev_set_drvdata(&pdev->dev, NULL);
327127c5b17cSBrian Norris 
327227c5b17cSBrian Norris 	return 0;
327327c5b17cSBrian Norris }
327427c5b17cSBrian Norris EXPORT_SYMBOL_GPL(brcmnand_remove);
327527c5b17cSBrian Norris 
327627c5b17cSBrian Norris MODULE_LICENSE("GPL v2");
327727c5b17cSBrian Norris MODULE_AUTHOR("Kevin Cernekee");
327827c5b17cSBrian Norris MODULE_AUTHOR("Brian Norris");
327927c5b17cSBrian Norris MODULE_DESCRIPTION("NAND driver for Broadcom chips");
328027c5b17cSBrian Norris MODULE_ALIAS("platform:brcmnand");
3281