162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Handles the M-Systems DiskOnChip G3 chip
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2011 Robert Jarzmik
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/kernel.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/errno.h>
1162306a36Sopenharmony_ci#include <linux/of.h>
1262306a36Sopenharmony_ci#include <linux/platform_device.h>
1362306a36Sopenharmony_ci#include <linux/string.h>
1462306a36Sopenharmony_ci#include <linux/slab.h>
1562306a36Sopenharmony_ci#include <linux/io.h>
1662306a36Sopenharmony_ci#include <linux/delay.h>
1762306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
1862306a36Sopenharmony_ci#include <linux/mtd/partitions.h>
1962306a36Sopenharmony_ci#include <linux/bitmap.h>
2062306a36Sopenharmony_ci#include <linux/bitrev.h>
2162306a36Sopenharmony_ci#include <linux/bch.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include <linux/debugfs.h>
2462306a36Sopenharmony_ci#include <linux/seq_file.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define CREATE_TRACE_POINTS
2762306a36Sopenharmony_ci#include "docg3.h"
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/*
3062306a36Sopenharmony_ci * This driver handles the DiskOnChip G3 flash memory.
3162306a36Sopenharmony_ci *
3262306a36Sopenharmony_ci * As no specification is available from M-Systems/Sandisk, this drivers lacks
3362306a36Sopenharmony_ci * several functions available on the chip, as :
3462306a36Sopenharmony_ci *  - IPL write
3562306a36Sopenharmony_ci *
3662306a36Sopenharmony_ci * The bus data width (8bits versus 16bits) is not handled (if_cfg flag), and
3762306a36Sopenharmony_ci * the driver assumes a 16bits data bus.
3862306a36Sopenharmony_ci *
3962306a36Sopenharmony_ci * DocG3 relies on 2 ECC algorithms, which are handled in hardware :
4062306a36Sopenharmony_ci *  - a 1 byte Hamming code stored in the OOB for each page
4162306a36Sopenharmony_ci *  - a 7 bytes BCH code stored in the OOB for each page
4262306a36Sopenharmony_ci * The BCH ECC is :
4362306a36Sopenharmony_ci *  - BCH is in GF(2^14)
4462306a36Sopenharmony_ci *  - BCH is over data of 520 bytes (512 page + 7 page_info bytes
4562306a36Sopenharmony_ci *                                   + 1 hamming byte)
4662306a36Sopenharmony_ci *  - BCH can correct up to 4 bits (t = 4)
4762306a36Sopenharmony_ci *  - BCH syndroms are calculated in hardware, and checked in hardware as well
4862306a36Sopenharmony_ci *
4962306a36Sopenharmony_ci */
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic unsigned int reliable_mode;
5262306a36Sopenharmony_cimodule_param(reliable_mode, uint, 0);
5362306a36Sopenharmony_ciMODULE_PARM_DESC(reliable_mode, "Set the docg3 mode (0=normal MLC, 1=fast, "
5462306a36Sopenharmony_ci		 "2=reliable) : MLC normal operations are in normal mode");
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic int docg3_ooblayout_ecc(struct mtd_info *mtd, int section,
5762306a36Sopenharmony_ci			       struct mtd_oob_region *oobregion)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	if (section)
6062306a36Sopenharmony_ci		return -ERANGE;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	/* byte 7 is Hamming ECC, byte 8-14 are BCH ECC */
6362306a36Sopenharmony_ci	oobregion->offset = 7;
6462306a36Sopenharmony_ci	oobregion->length = 8;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	return 0;
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic int docg3_ooblayout_free(struct mtd_info *mtd, int section,
7062306a36Sopenharmony_ci				struct mtd_oob_region *oobregion)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	if (section > 1)
7362306a36Sopenharmony_ci		return -ERANGE;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	/* free bytes: byte 0 until byte 6, byte 15 */
7662306a36Sopenharmony_ci	if (!section) {
7762306a36Sopenharmony_ci		oobregion->offset = 0;
7862306a36Sopenharmony_ci		oobregion->length = 7;
7962306a36Sopenharmony_ci	} else {
8062306a36Sopenharmony_ci		oobregion->offset = 15;
8162306a36Sopenharmony_ci		oobregion->length = 1;
8262306a36Sopenharmony_ci	}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	return 0;
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic const struct mtd_ooblayout_ops nand_ooblayout_docg3_ops = {
8862306a36Sopenharmony_ci	.ecc = docg3_ooblayout_ecc,
8962306a36Sopenharmony_ci	.free = docg3_ooblayout_free,
9062306a36Sopenharmony_ci};
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic inline u8 doc_readb(struct docg3 *docg3, u16 reg)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	u8 val = readb(docg3->cascade->base + reg);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	trace_docg3_io(0, 8, reg, (int)val);
9762306a36Sopenharmony_ci	return val;
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic inline u16 doc_readw(struct docg3 *docg3, u16 reg)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	u16 val = readw(docg3->cascade->base + reg);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	trace_docg3_io(0, 16, reg, (int)val);
10562306a36Sopenharmony_ci	return val;
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic inline void doc_writeb(struct docg3 *docg3, u8 val, u16 reg)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	writeb(val, docg3->cascade->base + reg);
11162306a36Sopenharmony_ci	trace_docg3_io(1, 8, reg, val);
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistatic inline void doc_writew(struct docg3 *docg3, u16 val, u16 reg)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	writew(val, docg3->cascade->base + reg);
11762306a36Sopenharmony_ci	trace_docg3_io(1, 16, reg, val);
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_cistatic inline void doc_flash_command(struct docg3 *docg3, u8 cmd)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	doc_writeb(docg3, cmd, DOC_FLASHCOMMAND);
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic inline void doc_flash_sequence(struct docg3 *docg3, u8 seq)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	doc_writeb(docg3, seq, DOC_FLASHSEQUENCE);
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic inline void doc_flash_address(struct docg3 *docg3, u8 addr)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	doc_writeb(docg3, addr, DOC_FLASHADDRESS);
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic char const * const part_probes[] = { "cmdlinepart", "saftlpart", NULL };
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic int doc_register_readb(struct docg3 *docg3, int reg)
13862306a36Sopenharmony_ci{
13962306a36Sopenharmony_ci	u8 val;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	doc_writew(docg3, reg, DOC_READADDRESS);
14262306a36Sopenharmony_ci	val = doc_readb(docg3, reg);
14362306a36Sopenharmony_ci	doc_vdbg("Read register %04x : %02x\n", reg, val);
14462306a36Sopenharmony_ci	return val;
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic int doc_register_readw(struct docg3 *docg3, int reg)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	u16 val;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	doc_writew(docg3, reg, DOC_READADDRESS);
15262306a36Sopenharmony_ci	val = doc_readw(docg3, reg);
15362306a36Sopenharmony_ci	doc_vdbg("Read register %04x : %04x\n", reg, val);
15462306a36Sopenharmony_ci	return val;
15562306a36Sopenharmony_ci}
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci/**
15862306a36Sopenharmony_ci * doc_delay - delay docg3 operations
15962306a36Sopenharmony_ci * @docg3: the device
16062306a36Sopenharmony_ci * @nbNOPs: the number of NOPs to issue
16162306a36Sopenharmony_ci *
16262306a36Sopenharmony_ci * As no specification is available, the right timings between chip commands are
16362306a36Sopenharmony_ci * unknown. The only available piece of information are the observed nops on a
16462306a36Sopenharmony_ci * working docg3 chip.
16562306a36Sopenharmony_ci * Therefore, doc_delay relies on a busy loop of NOPs, instead of scheduler
16662306a36Sopenharmony_ci * friendlier msleep() functions or blocking mdelay().
16762306a36Sopenharmony_ci */
16862306a36Sopenharmony_cistatic void doc_delay(struct docg3 *docg3, int nbNOPs)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	int i;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	doc_vdbg("NOP x %d\n", nbNOPs);
17362306a36Sopenharmony_ci	for (i = 0; i < nbNOPs; i++)
17462306a36Sopenharmony_ci		doc_writeb(docg3, 0, DOC_NOP);
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic int is_prot_seq_error(struct docg3 *docg3)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	int ctrl;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	ctrl = doc_register_readb(docg3, DOC_FLASHCONTROL);
18262306a36Sopenharmony_ci	return ctrl & (DOC_CTRL_PROTECTION_ERROR | DOC_CTRL_SEQUENCE_ERROR);
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic int doc_is_ready(struct docg3 *docg3)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	int ctrl;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	ctrl = doc_register_readb(docg3, DOC_FLASHCONTROL);
19062306a36Sopenharmony_ci	return ctrl & DOC_CTRL_FLASHREADY;
19162306a36Sopenharmony_ci}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_cistatic int doc_wait_ready(struct docg3 *docg3)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	int maxWaitCycles = 100;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	do {
19862306a36Sopenharmony_ci		doc_delay(docg3, 4);
19962306a36Sopenharmony_ci		cpu_relax();
20062306a36Sopenharmony_ci	} while (!doc_is_ready(docg3) && maxWaitCycles--);
20162306a36Sopenharmony_ci	doc_delay(docg3, 2);
20262306a36Sopenharmony_ci	if (maxWaitCycles > 0)
20362306a36Sopenharmony_ci		return 0;
20462306a36Sopenharmony_ci	else
20562306a36Sopenharmony_ci		return -EIO;
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cistatic int doc_reset_seq(struct docg3 *docg3)
20962306a36Sopenharmony_ci{
21062306a36Sopenharmony_ci	int ret;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	doc_writeb(docg3, 0x10, DOC_FLASHCONTROL);
21362306a36Sopenharmony_ci	doc_flash_sequence(docg3, DOC_SEQ_RESET);
21462306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_RESET);
21562306a36Sopenharmony_ci	doc_delay(docg3, 2);
21662306a36Sopenharmony_ci	ret = doc_wait_ready(docg3);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	doc_dbg("doc_reset_seq() -> isReady=%s\n", ret ? "false" : "true");
21962306a36Sopenharmony_ci	return ret;
22062306a36Sopenharmony_ci}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci/**
22362306a36Sopenharmony_ci * doc_read_data_area - Read data from data area
22462306a36Sopenharmony_ci * @docg3: the device
22562306a36Sopenharmony_ci * @buf: the buffer to fill in (might be NULL is dummy reads)
22662306a36Sopenharmony_ci * @len: the length to read
22762306a36Sopenharmony_ci * @first: first time read, DOC_READADDRESS should be set
22862306a36Sopenharmony_ci *
22962306a36Sopenharmony_ci * Reads bytes from flash data. Handles the single byte / even bytes reads.
23062306a36Sopenharmony_ci */
23162306a36Sopenharmony_cistatic void doc_read_data_area(struct docg3 *docg3, void *buf, int len,
23262306a36Sopenharmony_ci			       int first)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	int i, cdr, len4;
23562306a36Sopenharmony_ci	u16 data16, *dst16;
23662306a36Sopenharmony_ci	u8 data8, *dst8;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	doc_dbg("doc_read_data_area(buf=%p, len=%d)\n", buf, len);
23962306a36Sopenharmony_ci	cdr = len & 0x1;
24062306a36Sopenharmony_ci	len4 = len - cdr;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	if (first)
24362306a36Sopenharmony_ci		doc_writew(docg3, DOC_IOSPACE_DATA, DOC_READADDRESS);
24462306a36Sopenharmony_ci	dst16 = buf;
24562306a36Sopenharmony_ci	for (i = 0; i < len4; i += 2) {
24662306a36Sopenharmony_ci		data16 = doc_readw(docg3, DOC_IOSPACE_DATA);
24762306a36Sopenharmony_ci		if (dst16) {
24862306a36Sopenharmony_ci			*dst16 = data16;
24962306a36Sopenharmony_ci			dst16++;
25062306a36Sopenharmony_ci		}
25162306a36Sopenharmony_ci	}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	if (cdr) {
25462306a36Sopenharmony_ci		doc_writew(docg3, DOC_IOSPACE_DATA | DOC_READADDR_ONE_BYTE,
25562306a36Sopenharmony_ci			   DOC_READADDRESS);
25662306a36Sopenharmony_ci		doc_delay(docg3, 1);
25762306a36Sopenharmony_ci		dst8 = (u8 *)dst16;
25862306a36Sopenharmony_ci		for (i = 0; i < cdr; i++) {
25962306a36Sopenharmony_ci			data8 = doc_readb(docg3, DOC_IOSPACE_DATA);
26062306a36Sopenharmony_ci			if (dst8) {
26162306a36Sopenharmony_ci				*dst8 = data8;
26262306a36Sopenharmony_ci				dst8++;
26362306a36Sopenharmony_ci			}
26462306a36Sopenharmony_ci		}
26562306a36Sopenharmony_ci	}
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci/**
26962306a36Sopenharmony_ci * doc_write_data_area - Write data into data area
27062306a36Sopenharmony_ci * @docg3: the device
27162306a36Sopenharmony_ci * @buf: the buffer to get input bytes from
27262306a36Sopenharmony_ci * @len: the length to write
27362306a36Sopenharmony_ci *
27462306a36Sopenharmony_ci * Writes bytes into flash data. Handles the single byte / even bytes writes.
27562306a36Sopenharmony_ci */
27662306a36Sopenharmony_cistatic void doc_write_data_area(struct docg3 *docg3, const void *buf, int len)
27762306a36Sopenharmony_ci{
27862306a36Sopenharmony_ci	int i, cdr, len4;
27962306a36Sopenharmony_ci	u16 *src16;
28062306a36Sopenharmony_ci	u8 *src8;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	doc_dbg("doc_write_data_area(buf=%p, len=%d)\n", buf, len);
28362306a36Sopenharmony_ci	cdr = len & 0x3;
28462306a36Sopenharmony_ci	len4 = len - cdr;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	doc_writew(docg3, DOC_IOSPACE_DATA, DOC_READADDRESS);
28762306a36Sopenharmony_ci	src16 = (u16 *)buf;
28862306a36Sopenharmony_ci	for (i = 0; i < len4; i += 2) {
28962306a36Sopenharmony_ci		doc_writew(docg3, *src16, DOC_IOSPACE_DATA);
29062306a36Sopenharmony_ci		src16++;
29162306a36Sopenharmony_ci	}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	src8 = (u8 *)src16;
29462306a36Sopenharmony_ci	for (i = 0; i < cdr; i++) {
29562306a36Sopenharmony_ci		doc_writew(docg3, DOC_IOSPACE_DATA | DOC_READADDR_ONE_BYTE,
29662306a36Sopenharmony_ci			   DOC_READADDRESS);
29762306a36Sopenharmony_ci		doc_writeb(docg3, *src8, DOC_IOSPACE_DATA);
29862306a36Sopenharmony_ci		src8++;
29962306a36Sopenharmony_ci	}
30062306a36Sopenharmony_ci}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci/**
30362306a36Sopenharmony_ci * doc_set_reliable_mode - Sets the flash to normal or reliable data mode
30462306a36Sopenharmony_ci * @docg3: the device
30562306a36Sopenharmony_ci *
30662306a36Sopenharmony_ci * The reliable data mode is a bit slower than the fast mode, but less errors
30762306a36Sopenharmony_ci * occur.  Entering the reliable mode cannot be done without entering the fast
30862306a36Sopenharmony_ci * mode first.
30962306a36Sopenharmony_ci *
31062306a36Sopenharmony_ci * In reliable mode, pages 2*n and 2*n+1 are clones. Writing to page 0 of blocks
31162306a36Sopenharmony_ci * (4,5) make the hardware write also to page 1 of blocks blocks(4,5). Reading
31262306a36Sopenharmony_ci * from page 0 of blocks (4,5) or from page 1 of blocks (4,5) gives the same
31362306a36Sopenharmony_ci * result, which is a logical and between bytes from page 0 and page 1 (which is
31462306a36Sopenharmony_ci * consistent with the fact that writing to a page is _clearing_ bits of that
31562306a36Sopenharmony_ci * page).
31662306a36Sopenharmony_ci */
31762306a36Sopenharmony_cistatic void doc_set_reliable_mode(struct docg3 *docg3)
31862306a36Sopenharmony_ci{
31962306a36Sopenharmony_ci	static char *strmode[] = { "normal", "fast", "reliable", "invalid" };
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	doc_dbg("doc_set_reliable_mode(%s)\n", strmode[docg3->reliable]);
32262306a36Sopenharmony_ci	switch (docg3->reliable) {
32362306a36Sopenharmony_ci	case 0:
32462306a36Sopenharmony_ci		break;
32562306a36Sopenharmony_ci	case 1:
32662306a36Sopenharmony_ci		doc_flash_sequence(docg3, DOC_SEQ_SET_FASTMODE);
32762306a36Sopenharmony_ci		doc_flash_command(docg3, DOC_CMD_FAST_MODE);
32862306a36Sopenharmony_ci		break;
32962306a36Sopenharmony_ci	case 2:
33062306a36Sopenharmony_ci		doc_flash_sequence(docg3, DOC_SEQ_SET_RELIABLEMODE);
33162306a36Sopenharmony_ci		doc_flash_command(docg3, DOC_CMD_FAST_MODE);
33262306a36Sopenharmony_ci		doc_flash_command(docg3, DOC_CMD_RELIABLE_MODE);
33362306a36Sopenharmony_ci		break;
33462306a36Sopenharmony_ci	default:
33562306a36Sopenharmony_ci		doc_err("doc_set_reliable_mode(): invalid mode\n");
33662306a36Sopenharmony_ci		break;
33762306a36Sopenharmony_ci	}
33862306a36Sopenharmony_ci	doc_delay(docg3, 2);
33962306a36Sopenharmony_ci}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci/**
34262306a36Sopenharmony_ci * doc_set_asic_mode - Set the ASIC mode
34362306a36Sopenharmony_ci * @docg3: the device
34462306a36Sopenharmony_ci * @mode: the mode
34562306a36Sopenharmony_ci *
34662306a36Sopenharmony_ci * The ASIC can work in 3 modes :
34762306a36Sopenharmony_ci *  - RESET: all registers are zeroed
34862306a36Sopenharmony_ci *  - NORMAL: receives and handles commands
34962306a36Sopenharmony_ci *  - POWERDOWN: minimal poweruse, flash parts shut off
35062306a36Sopenharmony_ci */
35162306a36Sopenharmony_cistatic void doc_set_asic_mode(struct docg3 *docg3, u8 mode)
35262306a36Sopenharmony_ci{
35362306a36Sopenharmony_ci	int i;
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	for (i = 0; i < 12; i++)
35662306a36Sopenharmony_ci		doc_readb(docg3, DOC_IOSPACE_IPL);
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	mode |= DOC_ASICMODE_MDWREN;
35962306a36Sopenharmony_ci	doc_dbg("doc_set_asic_mode(%02x)\n", mode);
36062306a36Sopenharmony_ci	doc_writeb(docg3, mode, DOC_ASICMODE);
36162306a36Sopenharmony_ci	doc_writeb(docg3, ~mode, DOC_ASICMODECONFIRM);
36262306a36Sopenharmony_ci	doc_delay(docg3, 1);
36362306a36Sopenharmony_ci}
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci/**
36662306a36Sopenharmony_ci * doc_set_device_id - Sets the devices id for cascaded G3 chips
36762306a36Sopenharmony_ci * @docg3: the device
36862306a36Sopenharmony_ci * @id: the chip to select (amongst 0, 1, 2, 3)
36962306a36Sopenharmony_ci *
37062306a36Sopenharmony_ci * There can be 4 cascaded G3 chips. This function selects the one which will
37162306a36Sopenharmony_ci * should be the active one.
37262306a36Sopenharmony_ci */
37362306a36Sopenharmony_cistatic void doc_set_device_id(struct docg3 *docg3, int id)
37462306a36Sopenharmony_ci{
37562306a36Sopenharmony_ci	u8 ctrl;
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	doc_dbg("doc_set_device_id(%d)\n", id);
37862306a36Sopenharmony_ci	doc_writeb(docg3, id, DOC_DEVICESELECT);
37962306a36Sopenharmony_ci	ctrl = doc_register_readb(docg3, DOC_FLASHCONTROL);
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	ctrl &= ~DOC_CTRL_VIOLATION;
38262306a36Sopenharmony_ci	ctrl |= DOC_CTRL_CE;
38362306a36Sopenharmony_ci	doc_writeb(docg3, ctrl, DOC_FLASHCONTROL);
38462306a36Sopenharmony_ci}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci/**
38762306a36Sopenharmony_ci * doc_set_extra_page_mode - Change flash page layout
38862306a36Sopenharmony_ci * @docg3: the device
38962306a36Sopenharmony_ci *
39062306a36Sopenharmony_ci * Normally, the flash page is split into the data (512 bytes) and the out of
39162306a36Sopenharmony_ci * band data (16 bytes). For each, 4 more bytes can be accessed, where the wear
39262306a36Sopenharmony_ci * leveling counters are stored.  To access this last area of 4 bytes, a special
39362306a36Sopenharmony_ci * mode must be input to the flash ASIC.
39462306a36Sopenharmony_ci *
39562306a36Sopenharmony_ci * Returns 0 if no error occurred, -EIO else.
39662306a36Sopenharmony_ci */
39762306a36Sopenharmony_cistatic int doc_set_extra_page_mode(struct docg3 *docg3)
39862306a36Sopenharmony_ci{
39962306a36Sopenharmony_ci	int fctrl;
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	doc_dbg("doc_set_extra_page_mode()\n");
40262306a36Sopenharmony_ci	doc_flash_sequence(docg3, DOC_SEQ_PAGE_SIZE_532);
40362306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_PAGE_SIZE_532);
40462306a36Sopenharmony_ci	doc_delay(docg3, 2);
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	fctrl = doc_register_readb(docg3, DOC_FLASHCONTROL);
40762306a36Sopenharmony_ci	if (fctrl & (DOC_CTRL_PROTECTION_ERROR | DOC_CTRL_SEQUENCE_ERROR))
40862306a36Sopenharmony_ci		return -EIO;
40962306a36Sopenharmony_ci	else
41062306a36Sopenharmony_ci		return 0;
41162306a36Sopenharmony_ci}
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci/**
41462306a36Sopenharmony_ci * doc_setup_addr_sector - Setup blocks/page/ofs address for one plane
41562306a36Sopenharmony_ci * @docg3: the device
41662306a36Sopenharmony_ci * @sector: the sector
41762306a36Sopenharmony_ci */
41862306a36Sopenharmony_cistatic void doc_setup_addr_sector(struct docg3 *docg3, int sector)
41962306a36Sopenharmony_ci{
42062306a36Sopenharmony_ci	doc_delay(docg3, 1);
42162306a36Sopenharmony_ci	doc_flash_address(docg3, sector & 0xff);
42262306a36Sopenharmony_ci	doc_flash_address(docg3, (sector >> 8) & 0xff);
42362306a36Sopenharmony_ci	doc_flash_address(docg3, (sector >> 16) & 0xff);
42462306a36Sopenharmony_ci	doc_delay(docg3, 1);
42562306a36Sopenharmony_ci}
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci/**
42862306a36Sopenharmony_ci * doc_setup_writeaddr_sector - Setup blocks/page/ofs address for one plane
42962306a36Sopenharmony_ci * @docg3: the device
43062306a36Sopenharmony_ci * @sector: the sector
43162306a36Sopenharmony_ci * @ofs: the offset in the page, between 0 and (512 + 16 + 512)
43262306a36Sopenharmony_ci */
43362306a36Sopenharmony_cistatic void doc_setup_writeaddr_sector(struct docg3 *docg3, int sector, int ofs)
43462306a36Sopenharmony_ci{
43562306a36Sopenharmony_ci	ofs = ofs >> 2;
43662306a36Sopenharmony_ci	doc_delay(docg3, 1);
43762306a36Sopenharmony_ci	doc_flash_address(docg3, ofs & 0xff);
43862306a36Sopenharmony_ci	doc_flash_address(docg3, sector & 0xff);
43962306a36Sopenharmony_ci	doc_flash_address(docg3, (sector >> 8) & 0xff);
44062306a36Sopenharmony_ci	doc_flash_address(docg3, (sector >> 16) & 0xff);
44162306a36Sopenharmony_ci	doc_delay(docg3, 1);
44262306a36Sopenharmony_ci}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci/**
44562306a36Sopenharmony_ci * doc_read_seek - Set both flash planes to the specified block, page for reading
44662306a36Sopenharmony_ci * @docg3: the device
44762306a36Sopenharmony_ci * @block0: the first plane block index
44862306a36Sopenharmony_ci * @block1: the second plane block index
44962306a36Sopenharmony_ci * @page: the page index within the block
45062306a36Sopenharmony_ci * @wear: if true, read will occur on the 4 extra bytes of the wear area
45162306a36Sopenharmony_ci * @ofs: offset in page to read
45262306a36Sopenharmony_ci *
45362306a36Sopenharmony_ci * Programs the flash even and odd planes to the specific block and page.
45462306a36Sopenharmony_ci * Alternatively, programs the flash to the wear area of the specified page.
45562306a36Sopenharmony_ci */
45662306a36Sopenharmony_cistatic int doc_read_seek(struct docg3 *docg3, int block0, int block1, int page,
45762306a36Sopenharmony_ci			 int wear, int ofs)
45862306a36Sopenharmony_ci{
45962306a36Sopenharmony_ci	int sector, ret = 0;
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	doc_dbg("doc_seek(blocks=(%d,%d), page=%d, ofs=%d, wear=%d)\n",
46262306a36Sopenharmony_ci		block0, block1, page, ofs, wear);
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	if (!wear && (ofs < 2 * DOC_LAYOUT_PAGE_SIZE)) {
46562306a36Sopenharmony_ci		doc_flash_sequence(docg3, DOC_SEQ_SET_PLANE1);
46662306a36Sopenharmony_ci		doc_flash_command(docg3, DOC_CMD_READ_PLANE1);
46762306a36Sopenharmony_ci		doc_delay(docg3, 2);
46862306a36Sopenharmony_ci	} else {
46962306a36Sopenharmony_ci		doc_flash_sequence(docg3, DOC_SEQ_SET_PLANE2);
47062306a36Sopenharmony_ci		doc_flash_command(docg3, DOC_CMD_READ_PLANE2);
47162306a36Sopenharmony_ci		doc_delay(docg3, 2);
47262306a36Sopenharmony_ci	}
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	doc_set_reliable_mode(docg3);
47562306a36Sopenharmony_ci	if (wear)
47662306a36Sopenharmony_ci		ret = doc_set_extra_page_mode(docg3);
47762306a36Sopenharmony_ci	if (ret)
47862306a36Sopenharmony_ci		goto out;
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	doc_flash_sequence(docg3, DOC_SEQ_READ);
48162306a36Sopenharmony_ci	sector = (block0 << DOC_ADDR_BLOCK_SHIFT) + (page & DOC_ADDR_PAGE_MASK);
48262306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_PROG_BLOCK_ADDR);
48362306a36Sopenharmony_ci	doc_setup_addr_sector(docg3, sector);
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	sector = (block1 << DOC_ADDR_BLOCK_SHIFT) + (page & DOC_ADDR_PAGE_MASK);
48662306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_PROG_BLOCK_ADDR);
48762306a36Sopenharmony_ci	doc_setup_addr_sector(docg3, sector);
48862306a36Sopenharmony_ci	doc_delay(docg3, 1);
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ciout:
49162306a36Sopenharmony_ci	return ret;
49262306a36Sopenharmony_ci}
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci/**
49562306a36Sopenharmony_ci * doc_write_seek - Set both flash planes to the specified block, page for writing
49662306a36Sopenharmony_ci * @docg3: the device
49762306a36Sopenharmony_ci * @block0: the first plane block index
49862306a36Sopenharmony_ci * @block1: the second plane block index
49962306a36Sopenharmony_ci * @page: the page index within the block
50062306a36Sopenharmony_ci * @ofs: offset in page to write
50162306a36Sopenharmony_ci *
50262306a36Sopenharmony_ci * Programs the flash even and odd planes to the specific block and page.
50362306a36Sopenharmony_ci * Alternatively, programs the flash to the wear area of the specified page.
50462306a36Sopenharmony_ci */
50562306a36Sopenharmony_cistatic int doc_write_seek(struct docg3 *docg3, int block0, int block1, int page,
50662306a36Sopenharmony_ci			 int ofs)
50762306a36Sopenharmony_ci{
50862306a36Sopenharmony_ci	int ret = 0, sector;
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	doc_dbg("doc_write_seek(blocks=(%d,%d), page=%d, ofs=%d)\n",
51162306a36Sopenharmony_ci		block0, block1, page, ofs);
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	doc_set_reliable_mode(docg3);
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	if (ofs < 2 * DOC_LAYOUT_PAGE_SIZE) {
51662306a36Sopenharmony_ci		doc_flash_sequence(docg3, DOC_SEQ_SET_PLANE1);
51762306a36Sopenharmony_ci		doc_flash_command(docg3, DOC_CMD_READ_PLANE1);
51862306a36Sopenharmony_ci		doc_delay(docg3, 2);
51962306a36Sopenharmony_ci	} else {
52062306a36Sopenharmony_ci		doc_flash_sequence(docg3, DOC_SEQ_SET_PLANE2);
52162306a36Sopenharmony_ci		doc_flash_command(docg3, DOC_CMD_READ_PLANE2);
52262306a36Sopenharmony_ci		doc_delay(docg3, 2);
52362306a36Sopenharmony_ci	}
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci	doc_flash_sequence(docg3, DOC_SEQ_PAGE_SETUP);
52662306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_PROG_CYCLE1);
52762306a36Sopenharmony_ci
52862306a36Sopenharmony_ci	sector = (block0 << DOC_ADDR_BLOCK_SHIFT) + (page & DOC_ADDR_PAGE_MASK);
52962306a36Sopenharmony_ci	doc_setup_writeaddr_sector(docg3, sector, ofs);
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_PROG_CYCLE3);
53262306a36Sopenharmony_ci	doc_delay(docg3, 2);
53362306a36Sopenharmony_ci	ret = doc_wait_ready(docg3);
53462306a36Sopenharmony_ci	if (ret)
53562306a36Sopenharmony_ci		goto out;
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_PROG_CYCLE1);
53862306a36Sopenharmony_ci	sector = (block1 << DOC_ADDR_BLOCK_SHIFT) + (page & DOC_ADDR_PAGE_MASK);
53962306a36Sopenharmony_ci	doc_setup_writeaddr_sector(docg3, sector, ofs);
54062306a36Sopenharmony_ci	doc_delay(docg3, 1);
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_ciout:
54362306a36Sopenharmony_ci	return ret;
54462306a36Sopenharmony_ci}
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_ci/**
54862306a36Sopenharmony_ci * doc_read_page_ecc_init - Initialize hardware ECC engine
54962306a36Sopenharmony_ci * @docg3: the device
55062306a36Sopenharmony_ci * @len: the number of bytes covered by the ECC (BCH covered)
55162306a36Sopenharmony_ci *
55262306a36Sopenharmony_ci * The function does initialize the hardware ECC engine to compute the Hamming
55362306a36Sopenharmony_ci * ECC (on 1 byte) and the BCH hardware ECC (on 7 bytes).
55462306a36Sopenharmony_ci *
55562306a36Sopenharmony_ci * Return 0 if succeeded, -EIO on error
55662306a36Sopenharmony_ci */
55762306a36Sopenharmony_cistatic int doc_read_page_ecc_init(struct docg3 *docg3, int len)
55862306a36Sopenharmony_ci{
55962306a36Sopenharmony_ci	doc_writew(docg3, DOC_ECCCONF0_READ_MODE
56062306a36Sopenharmony_ci		   | DOC_ECCCONF0_BCH_ENABLE | DOC_ECCCONF0_HAMMING_ENABLE
56162306a36Sopenharmony_ci		   | (len & DOC_ECCCONF0_DATA_BYTES_MASK),
56262306a36Sopenharmony_ci		   DOC_ECCCONF0);
56362306a36Sopenharmony_ci	doc_delay(docg3, 4);
56462306a36Sopenharmony_ci	doc_register_readb(docg3, DOC_FLASHCONTROL);
56562306a36Sopenharmony_ci	return doc_wait_ready(docg3);
56662306a36Sopenharmony_ci}
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci/**
56962306a36Sopenharmony_ci * doc_write_page_ecc_init - Initialize hardware BCH ECC engine
57062306a36Sopenharmony_ci * @docg3: the device
57162306a36Sopenharmony_ci * @len: the number of bytes covered by the ECC (BCH covered)
57262306a36Sopenharmony_ci *
57362306a36Sopenharmony_ci * The function does initialize the hardware ECC engine to compute the Hamming
57462306a36Sopenharmony_ci * ECC (on 1 byte) and the BCH hardware ECC (on 7 bytes).
57562306a36Sopenharmony_ci *
57662306a36Sopenharmony_ci * Return 0 if succeeded, -EIO on error
57762306a36Sopenharmony_ci */
57862306a36Sopenharmony_cistatic int doc_write_page_ecc_init(struct docg3 *docg3, int len)
57962306a36Sopenharmony_ci{
58062306a36Sopenharmony_ci	doc_writew(docg3, DOC_ECCCONF0_WRITE_MODE
58162306a36Sopenharmony_ci		   | DOC_ECCCONF0_BCH_ENABLE | DOC_ECCCONF0_HAMMING_ENABLE
58262306a36Sopenharmony_ci		   | (len & DOC_ECCCONF0_DATA_BYTES_MASK),
58362306a36Sopenharmony_ci		   DOC_ECCCONF0);
58462306a36Sopenharmony_ci	doc_delay(docg3, 4);
58562306a36Sopenharmony_ci	doc_register_readb(docg3, DOC_FLASHCONTROL);
58662306a36Sopenharmony_ci	return doc_wait_ready(docg3);
58762306a36Sopenharmony_ci}
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci/**
59062306a36Sopenharmony_ci * doc_ecc_disable - Disable Hamming and BCH ECC hardware calculator
59162306a36Sopenharmony_ci * @docg3: the device
59262306a36Sopenharmony_ci *
59362306a36Sopenharmony_ci * Disables the hardware ECC generator and checker, for unchecked reads (as when
59462306a36Sopenharmony_ci * reading OOB only or write status byte).
59562306a36Sopenharmony_ci */
59662306a36Sopenharmony_cistatic void doc_ecc_disable(struct docg3 *docg3)
59762306a36Sopenharmony_ci{
59862306a36Sopenharmony_ci	doc_writew(docg3, DOC_ECCCONF0_READ_MODE, DOC_ECCCONF0);
59962306a36Sopenharmony_ci	doc_delay(docg3, 4);
60062306a36Sopenharmony_ci}
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci/**
60362306a36Sopenharmony_ci * doc_hamming_ecc_init - Initialize hardware Hamming ECC engine
60462306a36Sopenharmony_ci * @docg3: the device
60562306a36Sopenharmony_ci * @nb_bytes: the number of bytes covered by the ECC (Hamming covered)
60662306a36Sopenharmony_ci *
60762306a36Sopenharmony_ci * This function programs the ECC hardware to compute the hamming code on the
60862306a36Sopenharmony_ci * last provided N bytes to the hardware generator.
60962306a36Sopenharmony_ci */
61062306a36Sopenharmony_cistatic void doc_hamming_ecc_init(struct docg3 *docg3, int nb_bytes)
61162306a36Sopenharmony_ci{
61262306a36Sopenharmony_ci	u8 ecc_conf1;
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	ecc_conf1 = doc_register_readb(docg3, DOC_ECCCONF1);
61562306a36Sopenharmony_ci	ecc_conf1 &= ~DOC_ECCCONF1_HAMMING_BITS_MASK;
61662306a36Sopenharmony_ci	ecc_conf1 |= (nb_bytes & DOC_ECCCONF1_HAMMING_BITS_MASK);
61762306a36Sopenharmony_ci	doc_writeb(docg3, ecc_conf1, DOC_ECCCONF1);
61862306a36Sopenharmony_ci}
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci/**
62162306a36Sopenharmony_ci * doc_ecc_bch_fix_data - Fix if need be read data from flash
62262306a36Sopenharmony_ci * @docg3: the device
62362306a36Sopenharmony_ci * @buf: the buffer of read data (512 + 7 + 1 bytes)
62462306a36Sopenharmony_ci * @hwecc: the hardware calculated ECC.
62562306a36Sopenharmony_ci *         It's in fact recv_ecc ^ calc_ecc, where recv_ecc was read from OOB
62662306a36Sopenharmony_ci *         area data, and calc_ecc the ECC calculated by the hardware generator.
62762306a36Sopenharmony_ci *
62862306a36Sopenharmony_ci * Checks if the received data matches the ECC, and if an error is detected,
62962306a36Sopenharmony_ci * tries to fix the bit flips (at most 4) in the buffer buf.  As the docg3
63062306a36Sopenharmony_ci * understands the (data, ecc, syndroms) in an inverted order in comparison to
63162306a36Sopenharmony_ci * the BCH library, the function reverses the order of bits (ie. bit7 and bit0,
63262306a36Sopenharmony_ci * bit6 and bit 1, ...) for all ECC data.
63362306a36Sopenharmony_ci *
63462306a36Sopenharmony_ci * The hardware ecc unit produces oob_ecc ^ calc_ecc.  The kernel's bch
63562306a36Sopenharmony_ci * algorithm is used to decode this.  However the hw operates on page
63662306a36Sopenharmony_ci * data in a bit order that is the reverse of that of the bch alg,
63762306a36Sopenharmony_ci * requiring that the bits be reversed on the result.  Thanks to Ivan
63862306a36Sopenharmony_ci * Djelic for his analysis.
63962306a36Sopenharmony_ci *
64062306a36Sopenharmony_ci * Returns number of fixed bits (0, 1, 2, 3, 4) or -EBADMSG if too many bit
64162306a36Sopenharmony_ci * errors were detected and cannot be fixed.
64262306a36Sopenharmony_ci */
64362306a36Sopenharmony_cistatic int doc_ecc_bch_fix_data(struct docg3 *docg3, void *buf, u8 *hwecc)
64462306a36Sopenharmony_ci{
64562306a36Sopenharmony_ci	u8 ecc[DOC_ECC_BCH_SIZE];
64662306a36Sopenharmony_ci	int errorpos[DOC_ECC_BCH_T], i, numerrs;
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	for (i = 0; i < DOC_ECC_BCH_SIZE; i++)
64962306a36Sopenharmony_ci		ecc[i] = bitrev8(hwecc[i]);
65062306a36Sopenharmony_ci	numerrs = bch_decode(docg3->cascade->bch, NULL,
65162306a36Sopenharmony_ci			     DOC_ECC_BCH_COVERED_BYTES,
65262306a36Sopenharmony_ci			     NULL, ecc, NULL, errorpos);
65362306a36Sopenharmony_ci	BUG_ON(numerrs == -EINVAL);
65462306a36Sopenharmony_ci	if (numerrs < 0)
65562306a36Sopenharmony_ci		goto out;
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_ci	for (i = 0; i < numerrs; i++)
65862306a36Sopenharmony_ci		errorpos[i] = (errorpos[i] & ~7) | (7 - (errorpos[i] & 7));
65962306a36Sopenharmony_ci	for (i = 0; i < numerrs; i++)
66062306a36Sopenharmony_ci		if (errorpos[i] < DOC_ECC_BCH_COVERED_BYTES*8)
66162306a36Sopenharmony_ci			/* error is located in data, correct it */
66262306a36Sopenharmony_ci			change_bit(errorpos[i], buf);
66362306a36Sopenharmony_ciout:
66462306a36Sopenharmony_ci	doc_dbg("doc_ecc_bch_fix_data: flipped %d bits\n", numerrs);
66562306a36Sopenharmony_ci	return numerrs;
66662306a36Sopenharmony_ci}
66762306a36Sopenharmony_ci
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_ci/**
67062306a36Sopenharmony_ci * doc_read_page_prepare - Prepares reading data from a flash page
67162306a36Sopenharmony_ci * @docg3: the device
67262306a36Sopenharmony_ci * @block0: the first plane block index on flash memory
67362306a36Sopenharmony_ci * @block1: the second plane block index on flash memory
67462306a36Sopenharmony_ci * @page: the page index in the block
67562306a36Sopenharmony_ci * @offset: the offset in the page (must be a multiple of 4)
67662306a36Sopenharmony_ci *
67762306a36Sopenharmony_ci * Prepares the page to be read in the flash memory :
67862306a36Sopenharmony_ci *   - tell ASIC to map the flash pages
67962306a36Sopenharmony_ci *   - tell ASIC to be in read mode
68062306a36Sopenharmony_ci *
68162306a36Sopenharmony_ci * After a call to this method, a call to doc_read_page_finish is mandatory,
68262306a36Sopenharmony_ci * to end the read cycle of the flash.
68362306a36Sopenharmony_ci *
68462306a36Sopenharmony_ci * Read data from a flash page. The length to be read must be between 0 and
68562306a36Sopenharmony_ci * (page_size + oob_size + wear_size), ie. 532, and a multiple of 4 (because
68662306a36Sopenharmony_ci * the extra bytes reading is not implemented).
68762306a36Sopenharmony_ci *
68862306a36Sopenharmony_ci * As pages are grouped by 2 (in 2 planes), reading from a page must be done
68962306a36Sopenharmony_ci * in two steps:
69062306a36Sopenharmony_ci *  - one read of 512 bytes at offset 0
69162306a36Sopenharmony_ci *  - one read of 512 bytes at offset 512 + 16
69262306a36Sopenharmony_ci *
69362306a36Sopenharmony_ci * Returns 0 if successful, -EIO if a read error occurred.
69462306a36Sopenharmony_ci */
69562306a36Sopenharmony_cistatic int doc_read_page_prepare(struct docg3 *docg3, int block0, int block1,
69662306a36Sopenharmony_ci				 int page, int offset)
69762306a36Sopenharmony_ci{
69862306a36Sopenharmony_ci	int wear_area = 0, ret = 0;
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ci	doc_dbg("doc_read_page_prepare(blocks=(%d,%d), page=%d, ofsInPage=%d)\n",
70162306a36Sopenharmony_ci		block0, block1, page, offset);
70262306a36Sopenharmony_ci	if (offset >= DOC_LAYOUT_WEAR_OFFSET)
70362306a36Sopenharmony_ci		wear_area = 1;
70462306a36Sopenharmony_ci	if (!wear_area && offset > (DOC_LAYOUT_PAGE_OOB_SIZE * 2))
70562306a36Sopenharmony_ci		return -EINVAL;
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_ci	doc_set_device_id(docg3, docg3->device_id);
70862306a36Sopenharmony_ci	ret = doc_reset_seq(docg3);
70962306a36Sopenharmony_ci	if (ret)
71062306a36Sopenharmony_ci		goto err;
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_ci	/* Program the flash address block and page */
71362306a36Sopenharmony_ci	ret = doc_read_seek(docg3, block0, block1, page, wear_area, offset);
71462306a36Sopenharmony_ci	if (ret)
71562306a36Sopenharmony_ci		goto err;
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_READ_ALL_PLANES);
71862306a36Sopenharmony_ci	doc_delay(docg3, 2);
71962306a36Sopenharmony_ci	doc_wait_ready(docg3);
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_SET_ADDR_READ);
72262306a36Sopenharmony_ci	doc_delay(docg3, 1);
72362306a36Sopenharmony_ci	if (offset >= DOC_LAYOUT_PAGE_SIZE * 2)
72462306a36Sopenharmony_ci		offset -= 2 * DOC_LAYOUT_PAGE_SIZE;
72562306a36Sopenharmony_ci	doc_flash_address(docg3, offset >> 2);
72662306a36Sopenharmony_ci	doc_delay(docg3, 1);
72762306a36Sopenharmony_ci	doc_wait_ready(docg3);
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_READ_FLASH);
73062306a36Sopenharmony_ci
73162306a36Sopenharmony_ci	return 0;
73262306a36Sopenharmony_cierr:
73362306a36Sopenharmony_ci	doc_writeb(docg3, 0, DOC_DATAEND);
73462306a36Sopenharmony_ci	doc_delay(docg3, 2);
73562306a36Sopenharmony_ci	return -EIO;
73662306a36Sopenharmony_ci}
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_ci/**
73962306a36Sopenharmony_ci * doc_read_page_getbytes - Reads bytes from a prepared page
74062306a36Sopenharmony_ci * @docg3: the device
74162306a36Sopenharmony_ci * @len: the number of bytes to be read (must be a multiple of 4)
74262306a36Sopenharmony_ci * @buf: the buffer to be filled in (or NULL is forget bytes)
74362306a36Sopenharmony_ci * @first: 1 if first time read, DOC_READADDRESS should be set
74462306a36Sopenharmony_ci * @last_odd: 1 if last read ended up on an odd byte
74562306a36Sopenharmony_ci *
74662306a36Sopenharmony_ci * Reads bytes from a prepared page. There is a trickery here : if the last read
74762306a36Sopenharmony_ci * ended up on an odd offset in the 1024 bytes double page, ie. between the 2
74862306a36Sopenharmony_ci * planes, the first byte must be read apart. If a word (16bit) read was used,
74962306a36Sopenharmony_ci * the read would return the byte of plane 2 as low *and* high endian, which
75062306a36Sopenharmony_ci * will mess the read.
75162306a36Sopenharmony_ci *
75262306a36Sopenharmony_ci */
75362306a36Sopenharmony_cistatic int doc_read_page_getbytes(struct docg3 *docg3, int len, u_char *buf,
75462306a36Sopenharmony_ci				  int first, int last_odd)
75562306a36Sopenharmony_ci{
75662306a36Sopenharmony_ci	if (last_odd && len > 0) {
75762306a36Sopenharmony_ci		doc_read_data_area(docg3, buf, 1, first);
75862306a36Sopenharmony_ci		doc_read_data_area(docg3, buf ? buf + 1 : buf, len - 1, 0);
75962306a36Sopenharmony_ci	} else {
76062306a36Sopenharmony_ci		doc_read_data_area(docg3, buf, len, first);
76162306a36Sopenharmony_ci	}
76262306a36Sopenharmony_ci	doc_delay(docg3, 2);
76362306a36Sopenharmony_ci	return len;
76462306a36Sopenharmony_ci}
76562306a36Sopenharmony_ci
76662306a36Sopenharmony_ci/**
76762306a36Sopenharmony_ci * doc_write_page_putbytes - Writes bytes into a prepared page
76862306a36Sopenharmony_ci * @docg3: the device
76962306a36Sopenharmony_ci * @len: the number of bytes to be written
77062306a36Sopenharmony_ci * @buf: the buffer of input bytes
77162306a36Sopenharmony_ci *
77262306a36Sopenharmony_ci */
77362306a36Sopenharmony_cistatic void doc_write_page_putbytes(struct docg3 *docg3, int len,
77462306a36Sopenharmony_ci				    const u_char *buf)
77562306a36Sopenharmony_ci{
77662306a36Sopenharmony_ci	doc_write_data_area(docg3, buf, len);
77762306a36Sopenharmony_ci	doc_delay(docg3, 2);
77862306a36Sopenharmony_ci}
77962306a36Sopenharmony_ci
78062306a36Sopenharmony_ci/**
78162306a36Sopenharmony_ci * doc_get_bch_hw_ecc - Get hardware calculated BCH ECC
78262306a36Sopenharmony_ci * @docg3: the device
78362306a36Sopenharmony_ci * @hwecc:  the array of 7 integers where the hardware ecc will be stored
78462306a36Sopenharmony_ci */
78562306a36Sopenharmony_cistatic void doc_get_bch_hw_ecc(struct docg3 *docg3, u8 *hwecc)
78662306a36Sopenharmony_ci{
78762306a36Sopenharmony_ci	int i;
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci	for (i = 0; i < DOC_ECC_BCH_SIZE; i++)
79062306a36Sopenharmony_ci		hwecc[i] = doc_register_readb(docg3, DOC_BCH_HW_ECC(i));
79162306a36Sopenharmony_ci}
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_ci/**
79462306a36Sopenharmony_ci * doc_page_finish - Ends reading/writing of a flash page
79562306a36Sopenharmony_ci * @docg3: the device
79662306a36Sopenharmony_ci */
79762306a36Sopenharmony_cistatic void doc_page_finish(struct docg3 *docg3)
79862306a36Sopenharmony_ci{
79962306a36Sopenharmony_ci	doc_writeb(docg3, 0, DOC_DATAEND);
80062306a36Sopenharmony_ci	doc_delay(docg3, 2);
80162306a36Sopenharmony_ci}
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_ci/**
80462306a36Sopenharmony_ci * doc_read_page_finish - Ends reading of a flash page
80562306a36Sopenharmony_ci * @docg3: the device
80662306a36Sopenharmony_ci *
80762306a36Sopenharmony_ci * As a side effect, resets the chip selector to 0. This ensures that after each
80862306a36Sopenharmony_ci * read operation, the floor 0 is selected. Therefore, if the systems halts, the
80962306a36Sopenharmony_ci * reboot will boot on floor 0, where the IPL is.
81062306a36Sopenharmony_ci */
81162306a36Sopenharmony_cistatic void doc_read_page_finish(struct docg3 *docg3)
81262306a36Sopenharmony_ci{
81362306a36Sopenharmony_ci	doc_page_finish(docg3);
81462306a36Sopenharmony_ci	doc_set_device_id(docg3, 0);
81562306a36Sopenharmony_ci}
81662306a36Sopenharmony_ci
81762306a36Sopenharmony_ci/**
81862306a36Sopenharmony_ci * calc_block_sector - Calculate blocks, pages and ofs.
81962306a36Sopenharmony_ci *
82062306a36Sopenharmony_ci * @from: offset in flash
82162306a36Sopenharmony_ci * @block0: first plane block index calculated
82262306a36Sopenharmony_ci * @block1: second plane block index calculated
82362306a36Sopenharmony_ci * @page: page calculated
82462306a36Sopenharmony_ci * @ofs: offset in page
82562306a36Sopenharmony_ci * @reliable: 0 if docg3 in normal mode, 1 if docg3 in fast mode, 2 if docg3 in
82662306a36Sopenharmony_ci * reliable mode.
82762306a36Sopenharmony_ci *
82862306a36Sopenharmony_ci * The calculation is based on the reliable/normal mode. In normal mode, the 64
82962306a36Sopenharmony_ci * pages of a block are available. In reliable mode, as pages 2*n and 2*n+1 are
83062306a36Sopenharmony_ci * clones, only 32 pages per block are available.
83162306a36Sopenharmony_ci */
83262306a36Sopenharmony_cistatic void calc_block_sector(loff_t from, int *block0, int *block1, int *page,
83362306a36Sopenharmony_ci			      int *ofs, int reliable)
83462306a36Sopenharmony_ci{
83562306a36Sopenharmony_ci	uint sector, pages_biblock;
83662306a36Sopenharmony_ci
83762306a36Sopenharmony_ci	pages_biblock = DOC_LAYOUT_PAGES_PER_BLOCK * DOC_LAYOUT_NBPLANES;
83862306a36Sopenharmony_ci	if (reliable == 1 || reliable == 2)
83962306a36Sopenharmony_ci		pages_biblock /= 2;
84062306a36Sopenharmony_ci
84162306a36Sopenharmony_ci	sector = from / DOC_LAYOUT_PAGE_SIZE;
84262306a36Sopenharmony_ci	*block0 = sector / pages_biblock * DOC_LAYOUT_NBPLANES;
84362306a36Sopenharmony_ci	*block1 = *block0 + 1;
84462306a36Sopenharmony_ci	*page = sector % pages_biblock;
84562306a36Sopenharmony_ci	*page /= DOC_LAYOUT_NBPLANES;
84662306a36Sopenharmony_ci	if (reliable == 1 || reliable == 2)
84762306a36Sopenharmony_ci		*page *= 2;
84862306a36Sopenharmony_ci	if (sector % 2)
84962306a36Sopenharmony_ci		*ofs = DOC_LAYOUT_PAGE_OOB_SIZE;
85062306a36Sopenharmony_ci	else
85162306a36Sopenharmony_ci		*ofs = 0;
85262306a36Sopenharmony_ci}
85362306a36Sopenharmony_ci
85462306a36Sopenharmony_ci/**
85562306a36Sopenharmony_ci * doc_read_oob - Read out of band bytes from flash
85662306a36Sopenharmony_ci * @mtd: the device
85762306a36Sopenharmony_ci * @from: the offset from first block and first page, in bytes, aligned on page
85862306a36Sopenharmony_ci *        size
85962306a36Sopenharmony_ci * @ops: the mtd oob structure
86062306a36Sopenharmony_ci *
86162306a36Sopenharmony_ci * Reads flash memory OOB area of pages.
86262306a36Sopenharmony_ci *
86362306a36Sopenharmony_ci * Returns 0 if read successful, of -EIO, -EINVAL if an error occurred
86462306a36Sopenharmony_ci */
86562306a36Sopenharmony_cistatic int doc_read_oob(struct mtd_info *mtd, loff_t from,
86662306a36Sopenharmony_ci			struct mtd_oob_ops *ops)
86762306a36Sopenharmony_ci{
86862306a36Sopenharmony_ci	struct docg3 *docg3 = mtd->priv;
86962306a36Sopenharmony_ci	int block0, block1, page, ret, skip, ofs = 0;
87062306a36Sopenharmony_ci	u8 *oobbuf = ops->oobbuf;
87162306a36Sopenharmony_ci	u8 *buf = ops->datbuf;
87262306a36Sopenharmony_ci	size_t len, ooblen, nbdata, nboob;
87362306a36Sopenharmony_ci	u8 hwecc[DOC_ECC_BCH_SIZE], eccconf1;
87462306a36Sopenharmony_ci	struct mtd_ecc_stats old_stats;
87562306a36Sopenharmony_ci	int max_bitflips = 0;
87662306a36Sopenharmony_ci
87762306a36Sopenharmony_ci	if (buf)
87862306a36Sopenharmony_ci		len = ops->len;
87962306a36Sopenharmony_ci	else
88062306a36Sopenharmony_ci		len = 0;
88162306a36Sopenharmony_ci	if (oobbuf)
88262306a36Sopenharmony_ci		ooblen = ops->ooblen;
88362306a36Sopenharmony_ci	else
88462306a36Sopenharmony_ci		ooblen = 0;
88562306a36Sopenharmony_ci
88662306a36Sopenharmony_ci	if (oobbuf && ops->mode == MTD_OPS_PLACE_OOB)
88762306a36Sopenharmony_ci		oobbuf += ops->ooboffs;
88862306a36Sopenharmony_ci
88962306a36Sopenharmony_ci	doc_dbg("doc_read_oob(from=%lld, mode=%d, data=(%p:%zu), oob=(%p:%zu))\n",
89062306a36Sopenharmony_ci		from, ops->mode, buf, len, oobbuf, ooblen);
89162306a36Sopenharmony_ci	if (ooblen % DOC_LAYOUT_OOB_SIZE)
89262306a36Sopenharmony_ci		return -EINVAL;
89362306a36Sopenharmony_ci
89462306a36Sopenharmony_ci	ops->oobretlen = 0;
89562306a36Sopenharmony_ci	ops->retlen = 0;
89662306a36Sopenharmony_ci	ret = 0;
89762306a36Sopenharmony_ci	skip = from % DOC_LAYOUT_PAGE_SIZE;
89862306a36Sopenharmony_ci	mutex_lock(&docg3->cascade->lock);
89962306a36Sopenharmony_ci	old_stats = mtd->ecc_stats;
90062306a36Sopenharmony_ci	while (ret >= 0 && (len > 0 || ooblen > 0)) {
90162306a36Sopenharmony_ci		calc_block_sector(from - skip, &block0, &block1, &page, &ofs,
90262306a36Sopenharmony_ci			docg3->reliable);
90362306a36Sopenharmony_ci		nbdata = min_t(size_t, len, DOC_LAYOUT_PAGE_SIZE - skip);
90462306a36Sopenharmony_ci		nboob = min_t(size_t, ooblen, (size_t)DOC_LAYOUT_OOB_SIZE);
90562306a36Sopenharmony_ci		ret = doc_read_page_prepare(docg3, block0, block1, page, ofs);
90662306a36Sopenharmony_ci		if (ret < 0)
90762306a36Sopenharmony_ci			goto out;
90862306a36Sopenharmony_ci		ret = doc_read_page_ecc_init(docg3, DOC_ECC_BCH_TOTAL_BYTES);
90962306a36Sopenharmony_ci		if (ret < 0)
91062306a36Sopenharmony_ci			goto err_in_read;
91162306a36Sopenharmony_ci		ret = doc_read_page_getbytes(docg3, skip, NULL, 1, 0);
91262306a36Sopenharmony_ci		if (ret < skip)
91362306a36Sopenharmony_ci			goto err_in_read;
91462306a36Sopenharmony_ci		ret = doc_read_page_getbytes(docg3, nbdata, buf, 0, skip % 2);
91562306a36Sopenharmony_ci		if (ret < nbdata)
91662306a36Sopenharmony_ci			goto err_in_read;
91762306a36Sopenharmony_ci		doc_read_page_getbytes(docg3,
91862306a36Sopenharmony_ci				       DOC_LAYOUT_PAGE_SIZE - nbdata - skip,
91962306a36Sopenharmony_ci				       NULL, 0, (skip + nbdata) % 2);
92062306a36Sopenharmony_ci		ret = doc_read_page_getbytes(docg3, nboob, oobbuf, 0, 0);
92162306a36Sopenharmony_ci		if (ret < nboob)
92262306a36Sopenharmony_ci			goto err_in_read;
92362306a36Sopenharmony_ci		doc_read_page_getbytes(docg3, DOC_LAYOUT_OOB_SIZE - nboob,
92462306a36Sopenharmony_ci				       NULL, 0, nboob % 2);
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_ci		doc_get_bch_hw_ecc(docg3, hwecc);
92762306a36Sopenharmony_ci		eccconf1 = doc_register_readb(docg3, DOC_ECCCONF1);
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_ci		if (nboob >= DOC_LAYOUT_OOB_SIZE) {
93062306a36Sopenharmony_ci			doc_dbg("OOB - INFO: %*phC\n", 7, oobbuf);
93162306a36Sopenharmony_ci			doc_dbg("OOB - HAMMING: %02x\n", oobbuf[7]);
93262306a36Sopenharmony_ci			doc_dbg("OOB - BCH_ECC: %*phC\n", 7, oobbuf + 8);
93362306a36Sopenharmony_ci			doc_dbg("OOB - UNUSED: %02x\n", oobbuf[15]);
93462306a36Sopenharmony_ci		}
93562306a36Sopenharmony_ci		doc_dbg("ECC checks: ECCConf1=%x\n", eccconf1);
93662306a36Sopenharmony_ci		doc_dbg("ECC HW_ECC: %*phC\n", 7, hwecc);
93762306a36Sopenharmony_ci
93862306a36Sopenharmony_ci		ret = -EIO;
93962306a36Sopenharmony_ci		if (is_prot_seq_error(docg3))
94062306a36Sopenharmony_ci			goto err_in_read;
94162306a36Sopenharmony_ci		ret = 0;
94262306a36Sopenharmony_ci		if ((block0 >= DOC_LAYOUT_BLOCK_FIRST_DATA) &&
94362306a36Sopenharmony_ci		    (eccconf1 & DOC_ECCCONF1_BCH_SYNDROM_ERR) &&
94462306a36Sopenharmony_ci		    (eccconf1 & DOC_ECCCONF1_PAGE_IS_WRITTEN) &&
94562306a36Sopenharmony_ci		    (ops->mode != MTD_OPS_RAW) &&
94662306a36Sopenharmony_ci		    (nbdata == DOC_LAYOUT_PAGE_SIZE)) {
94762306a36Sopenharmony_ci			ret = doc_ecc_bch_fix_data(docg3, buf, hwecc);
94862306a36Sopenharmony_ci			if (ret < 0) {
94962306a36Sopenharmony_ci				mtd->ecc_stats.failed++;
95062306a36Sopenharmony_ci				ret = -EBADMSG;
95162306a36Sopenharmony_ci			}
95262306a36Sopenharmony_ci			if (ret > 0) {
95362306a36Sopenharmony_ci				mtd->ecc_stats.corrected += ret;
95462306a36Sopenharmony_ci				max_bitflips = max(max_bitflips, ret);
95562306a36Sopenharmony_ci				ret = max_bitflips;
95662306a36Sopenharmony_ci			}
95762306a36Sopenharmony_ci		}
95862306a36Sopenharmony_ci
95962306a36Sopenharmony_ci		doc_read_page_finish(docg3);
96062306a36Sopenharmony_ci		ops->retlen += nbdata;
96162306a36Sopenharmony_ci		ops->oobretlen += nboob;
96262306a36Sopenharmony_ci		buf += nbdata;
96362306a36Sopenharmony_ci		oobbuf += nboob;
96462306a36Sopenharmony_ci		len -= nbdata;
96562306a36Sopenharmony_ci		ooblen -= nboob;
96662306a36Sopenharmony_ci		from += DOC_LAYOUT_PAGE_SIZE;
96762306a36Sopenharmony_ci		skip = 0;
96862306a36Sopenharmony_ci	}
96962306a36Sopenharmony_ci
97062306a36Sopenharmony_ciout:
97162306a36Sopenharmony_ci	if (ops->stats) {
97262306a36Sopenharmony_ci		ops->stats->uncorrectable_errors +=
97362306a36Sopenharmony_ci			mtd->ecc_stats.failed - old_stats.failed;
97462306a36Sopenharmony_ci		ops->stats->corrected_bitflips +=
97562306a36Sopenharmony_ci			mtd->ecc_stats.corrected - old_stats.corrected;
97662306a36Sopenharmony_ci	}
97762306a36Sopenharmony_ci	mutex_unlock(&docg3->cascade->lock);
97862306a36Sopenharmony_ci	return ret;
97962306a36Sopenharmony_cierr_in_read:
98062306a36Sopenharmony_ci	doc_read_page_finish(docg3);
98162306a36Sopenharmony_ci	goto out;
98262306a36Sopenharmony_ci}
98362306a36Sopenharmony_ci
98462306a36Sopenharmony_cistatic int doc_reload_bbt(struct docg3 *docg3)
98562306a36Sopenharmony_ci{
98662306a36Sopenharmony_ci	int block = DOC_LAYOUT_BLOCK_BBT;
98762306a36Sopenharmony_ci	int ret = 0, nbpages, page;
98862306a36Sopenharmony_ci	u_char *buf = docg3->bbt;
98962306a36Sopenharmony_ci
99062306a36Sopenharmony_ci	nbpages = DIV_ROUND_UP(docg3->max_block + 1, 8 * DOC_LAYOUT_PAGE_SIZE);
99162306a36Sopenharmony_ci	for (page = 0; !ret && (page < nbpages); page++) {
99262306a36Sopenharmony_ci		ret = doc_read_page_prepare(docg3, block, block + 1,
99362306a36Sopenharmony_ci					    page + DOC_LAYOUT_PAGE_BBT, 0);
99462306a36Sopenharmony_ci		if (!ret)
99562306a36Sopenharmony_ci			ret = doc_read_page_ecc_init(docg3,
99662306a36Sopenharmony_ci						     DOC_LAYOUT_PAGE_SIZE);
99762306a36Sopenharmony_ci		if (!ret)
99862306a36Sopenharmony_ci			doc_read_page_getbytes(docg3, DOC_LAYOUT_PAGE_SIZE,
99962306a36Sopenharmony_ci					       buf, 1, 0);
100062306a36Sopenharmony_ci		buf += DOC_LAYOUT_PAGE_SIZE;
100162306a36Sopenharmony_ci	}
100262306a36Sopenharmony_ci	doc_read_page_finish(docg3);
100362306a36Sopenharmony_ci	return ret;
100462306a36Sopenharmony_ci}
100562306a36Sopenharmony_ci
100662306a36Sopenharmony_ci/**
100762306a36Sopenharmony_ci * doc_block_isbad - Checks whether a block is good or not
100862306a36Sopenharmony_ci * @mtd: the device
100962306a36Sopenharmony_ci * @from: the offset to find the correct block
101062306a36Sopenharmony_ci *
101162306a36Sopenharmony_ci * Returns 1 if block is bad, 0 if block is good
101262306a36Sopenharmony_ci */
101362306a36Sopenharmony_cistatic int doc_block_isbad(struct mtd_info *mtd, loff_t from)
101462306a36Sopenharmony_ci{
101562306a36Sopenharmony_ci	struct docg3 *docg3 = mtd->priv;
101662306a36Sopenharmony_ci	int block0, block1, page, ofs, is_good;
101762306a36Sopenharmony_ci
101862306a36Sopenharmony_ci	calc_block_sector(from, &block0, &block1, &page, &ofs,
101962306a36Sopenharmony_ci		docg3->reliable);
102062306a36Sopenharmony_ci	doc_dbg("doc_block_isbad(from=%lld) => block=(%d,%d), page=%d, ofs=%d\n",
102162306a36Sopenharmony_ci		from, block0, block1, page, ofs);
102262306a36Sopenharmony_ci
102362306a36Sopenharmony_ci	if (block0 < DOC_LAYOUT_BLOCK_FIRST_DATA)
102462306a36Sopenharmony_ci		return 0;
102562306a36Sopenharmony_ci	if (block1 > docg3->max_block)
102662306a36Sopenharmony_ci		return -EINVAL;
102762306a36Sopenharmony_ci
102862306a36Sopenharmony_ci	is_good = docg3->bbt[block0 >> 3] & (1 << (block0 & 0x7));
102962306a36Sopenharmony_ci	return !is_good;
103062306a36Sopenharmony_ci}
103162306a36Sopenharmony_ci
103262306a36Sopenharmony_ci#if 0
103362306a36Sopenharmony_ci/**
103462306a36Sopenharmony_ci * doc_get_erase_count - Get block erase count
103562306a36Sopenharmony_ci * @docg3: the device
103662306a36Sopenharmony_ci * @from: the offset in which the block is.
103762306a36Sopenharmony_ci *
103862306a36Sopenharmony_ci * Get the number of times a block was erased. The number is the maximum of
103962306a36Sopenharmony_ci * erase times between first and second plane (which should be equal normally).
104062306a36Sopenharmony_ci *
104162306a36Sopenharmony_ci * Returns The number of erases, or -EINVAL or -EIO on error.
104262306a36Sopenharmony_ci */
104362306a36Sopenharmony_cistatic int doc_get_erase_count(struct docg3 *docg3, loff_t from)
104462306a36Sopenharmony_ci{
104562306a36Sopenharmony_ci	u8 buf[DOC_LAYOUT_WEAR_SIZE];
104662306a36Sopenharmony_ci	int ret, plane1_erase_count, plane2_erase_count;
104762306a36Sopenharmony_ci	int block0, block1, page, ofs;
104862306a36Sopenharmony_ci
104962306a36Sopenharmony_ci	doc_dbg("doc_get_erase_count(from=%lld, buf=%p)\n", from, buf);
105062306a36Sopenharmony_ci	if (from % DOC_LAYOUT_PAGE_SIZE)
105162306a36Sopenharmony_ci		return -EINVAL;
105262306a36Sopenharmony_ci	calc_block_sector(from, &block0, &block1, &page, &ofs, docg3->reliable);
105362306a36Sopenharmony_ci	if (block1 > docg3->max_block)
105462306a36Sopenharmony_ci		return -EINVAL;
105562306a36Sopenharmony_ci
105662306a36Sopenharmony_ci	ret = doc_reset_seq(docg3);
105762306a36Sopenharmony_ci	if (!ret)
105862306a36Sopenharmony_ci		ret = doc_read_page_prepare(docg3, block0, block1, page,
105962306a36Sopenharmony_ci					    ofs + DOC_LAYOUT_WEAR_OFFSET, 0);
106062306a36Sopenharmony_ci	if (!ret)
106162306a36Sopenharmony_ci		ret = doc_read_page_getbytes(docg3, DOC_LAYOUT_WEAR_SIZE,
106262306a36Sopenharmony_ci					     buf, 1, 0);
106362306a36Sopenharmony_ci	doc_read_page_finish(docg3);
106462306a36Sopenharmony_ci
106562306a36Sopenharmony_ci	if (ret || (buf[0] != DOC_ERASE_MARK) || (buf[2] != DOC_ERASE_MARK))
106662306a36Sopenharmony_ci		return -EIO;
106762306a36Sopenharmony_ci	plane1_erase_count = (u8)(~buf[1]) | ((u8)(~buf[4]) << 8)
106862306a36Sopenharmony_ci		| ((u8)(~buf[5]) << 16);
106962306a36Sopenharmony_ci	plane2_erase_count = (u8)(~buf[3]) | ((u8)(~buf[6]) << 8)
107062306a36Sopenharmony_ci		| ((u8)(~buf[7]) << 16);
107162306a36Sopenharmony_ci
107262306a36Sopenharmony_ci	return max(plane1_erase_count, plane2_erase_count);
107362306a36Sopenharmony_ci}
107462306a36Sopenharmony_ci#endif
107562306a36Sopenharmony_ci
107662306a36Sopenharmony_ci/**
107762306a36Sopenharmony_ci * doc_get_op_status - get erase/write operation status
107862306a36Sopenharmony_ci * @docg3: the device
107962306a36Sopenharmony_ci *
108062306a36Sopenharmony_ci * Queries the status from the chip, and returns it
108162306a36Sopenharmony_ci *
108262306a36Sopenharmony_ci * Returns the status (bits DOC_PLANES_STATUS_*)
108362306a36Sopenharmony_ci */
108462306a36Sopenharmony_cistatic int doc_get_op_status(struct docg3 *docg3)
108562306a36Sopenharmony_ci{
108662306a36Sopenharmony_ci	u8 status;
108762306a36Sopenharmony_ci
108862306a36Sopenharmony_ci	doc_flash_sequence(docg3, DOC_SEQ_PLANES_STATUS);
108962306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_PLANES_STATUS);
109062306a36Sopenharmony_ci	doc_delay(docg3, 5);
109162306a36Sopenharmony_ci
109262306a36Sopenharmony_ci	doc_ecc_disable(docg3);
109362306a36Sopenharmony_ci	doc_read_data_area(docg3, &status, 1, 1);
109462306a36Sopenharmony_ci	return status;
109562306a36Sopenharmony_ci}
109662306a36Sopenharmony_ci
109762306a36Sopenharmony_ci/**
109862306a36Sopenharmony_ci * doc_write_erase_wait_status - wait for write or erase completion
109962306a36Sopenharmony_ci * @docg3: the device
110062306a36Sopenharmony_ci *
110162306a36Sopenharmony_ci * Wait for the chip to be ready again after erase or write operation, and check
110262306a36Sopenharmony_ci * erase/write status.
110362306a36Sopenharmony_ci *
110462306a36Sopenharmony_ci * Returns 0 if erase successful, -EIO if erase/write issue, -ETIMEOUT if
110562306a36Sopenharmony_ci * timeout
110662306a36Sopenharmony_ci */
110762306a36Sopenharmony_cistatic int doc_write_erase_wait_status(struct docg3 *docg3)
110862306a36Sopenharmony_ci{
110962306a36Sopenharmony_ci	int i, status, ret = 0;
111062306a36Sopenharmony_ci
111162306a36Sopenharmony_ci	for (i = 0; !doc_is_ready(docg3) && i < 5; i++)
111262306a36Sopenharmony_ci		msleep(20);
111362306a36Sopenharmony_ci	if (!doc_is_ready(docg3)) {
111462306a36Sopenharmony_ci		doc_dbg("Timeout reached and the chip is still not ready\n");
111562306a36Sopenharmony_ci		ret = -EAGAIN;
111662306a36Sopenharmony_ci		goto out;
111762306a36Sopenharmony_ci	}
111862306a36Sopenharmony_ci
111962306a36Sopenharmony_ci	status = doc_get_op_status(docg3);
112062306a36Sopenharmony_ci	if (status & DOC_PLANES_STATUS_FAIL) {
112162306a36Sopenharmony_ci		doc_dbg("Erase/Write failed on (a) plane(s), status = %x\n",
112262306a36Sopenharmony_ci			status);
112362306a36Sopenharmony_ci		ret = -EIO;
112462306a36Sopenharmony_ci	}
112562306a36Sopenharmony_ci
112662306a36Sopenharmony_ciout:
112762306a36Sopenharmony_ci	doc_page_finish(docg3);
112862306a36Sopenharmony_ci	return ret;
112962306a36Sopenharmony_ci}
113062306a36Sopenharmony_ci
113162306a36Sopenharmony_ci/**
113262306a36Sopenharmony_ci * doc_erase_block - Erase a couple of blocks
113362306a36Sopenharmony_ci * @docg3: the device
113462306a36Sopenharmony_ci * @block0: the first block to erase (leftmost plane)
113562306a36Sopenharmony_ci * @block1: the second block to erase (rightmost plane)
113662306a36Sopenharmony_ci *
113762306a36Sopenharmony_ci * Erase both blocks, and return operation status
113862306a36Sopenharmony_ci *
113962306a36Sopenharmony_ci * Returns 0 if erase successful, -EIO if erase issue, -ETIMEOUT if chip not
114062306a36Sopenharmony_ci * ready for too long
114162306a36Sopenharmony_ci */
114262306a36Sopenharmony_cistatic int doc_erase_block(struct docg3 *docg3, int block0, int block1)
114362306a36Sopenharmony_ci{
114462306a36Sopenharmony_ci	int ret, sector;
114562306a36Sopenharmony_ci
114662306a36Sopenharmony_ci	doc_dbg("doc_erase_block(blocks=(%d,%d))\n", block0, block1);
114762306a36Sopenharmony_ci	ret = doc_reset_seq(docg3);
114862306a36Sopenharmony_ci	if (ret)
114962306a36Sopenharmony_ci		return -EIO;
115062306a36Sopenharmony_ci
115162306a36Sopenharmony_ci	doc_set_reliable_mode(docg3);
115262306a36Sopenharmony_ci	doc_flash_sequence(docg3, DOC_SEQ_ERASE);
115362306a36Sopenharmony_ci
115462306a36Sopenharmony_ci	sector = block0 << DOC_ADDR_BLOCK_SHIFT;
115562306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_PROG_BLOCK_ADDR);
115662306a36Sopenharmony_ci	doc_setup_addr_sector(docg3, sector);
115762306a36Sopenharmony_ci	sector = block1 << DOC_ADDR_BLOCK_SHIFT;
115862306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_PROG_BLOCK_ADDR);
115962306a36Sopenharmony_ci	doc_setup_addr_sector(docg3, sector);
116062306a36Sopenharmony_ci	doc_delay(docg3, 1);
116162306a36Sopenharmony_ci
116262306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_ERASECYCLE2);
116362306a36Sopenharmony_ci	doc_delay(docg3, 2);
116462306a36Sopenharmony_ci
116562306a36Sopenharmony_ci	if (is_prot_seq_error(docg3)) {
116662306a36Sopenharmony_ci		doc_err("Erase blocks %d,%d error\n", block0, block1);
116762306a36Sopenharmony_ci		return -EIO;
116862306a36Sopenharmony_ci	}
116962306a36Sopenharmony_ci
117062306a36Sopenharmony_ci	return doc_write_erase_wait_status(docg3);
117162306a36Sopenharmony_ci}
117262306a36Sopenharmony_ci
117362306a36Sopenharmony_ci/**
117462306a36Sopenharmony_ci * doc_erase - Erase a portion of the chip
117562306a36Sopenharmony_ci * @mtd: the device
117662306a36Sopenharmony_ci * @info: the erase info
117762306a36Sopenharmony_ci *
117862306a36Sopenharmony_ci * Erase a bunch of contiguous blocks, by pairs, as a "mtd" page of 1024 is
117962306a36Sopenharmony_ci * split into 2 pages of 512 bytes on 2 contiguous blocks.
118062306a36Sopenharmony_ci *
118162306a36Sopenharmony_ci * Returns 0 if erase successful, -EINVAL if addressing error, -EIO if erase
118262306a36Sopenharmony_ci * issue
118362306a36Sopenharmony_ci */
118462306a36Sopenharmony_cistatic int doc_erase(struct mtd_info *mtd, struct erase_info *info)
118562306a36Sopenharmony_ci{
118662306a36Sopenharmony_ci	struct docg3 *docg3 = mtd->priv;
118762306a36Sopenharmony_ci	uint64_t len;
118862306a36Sopenharmony_ci	int block0, block1, page, ret = 0, ofs = 0;
118962306a36Sopenharmony_ci
119062306a36Sopenharmony_ci	doc_dbg("doc_erase(from=%lld, len=%lld\n", info->addr, info->len);
119162306a36Sopenharmony_ci
119262306a36Sopenharmony_ci	calc_block_sector(info->addr + info->len, &block0, &block1, &page,
119362306a36Sopenharmony_ci			  &ofs, docg3->reliable);
119462306a36Sopenharmony_ci	if (info->addr + info->len > mtd->size || page || ofs)
119562306a36Sopenharmony_ci		return -EINVAL;
119662306a36Sopenharmony_ci
119762306a36Sopenharmony_ci	calc_block_sector(info->addr, &block0, &block1, &page, &ofs,
119862306a36Sopenharmony_ci			  docg3->reliable);
119962306a36Sopenharmony_ci	mutex_lock(&docg3->cascade->lock);
120062306a36Sopenharmony_ci	doc_set_device_id(docg3, docg3->device_id);
120162306a36Sopenharmony_ci	doc_set_reliable_mode(docg3);
120262306a36Sopenharmony_ci	for (len = info->len; !ret && len > 0; len -= mtd->erasesize) {
120362306a36Sopenharmony_ci		ret = doc_erase_block(docg3, block0, block1);
120462306a36Sopenharmony_ci		block0 += 2;
120562306a36Sopenharmony_ci		block1 += 2;
120662306a36Sopenharmony_ci	}
120762306a36Sopenharmony_ci	mutex_unlock(&docg3->cascade->lock);
120862306a36Sopenharmony_ci
120962306a36Sopenharmony_ci	return ret;
121062306a36Sopenharmony_ci}
121162306a36Sopenharmony_ci
121262306a36Sopenharmony_ci/**
121362306a36Sopenharmony_ci * doc_write_page - Write a single page to the chip
121462306a36Sopenharmony_ci * @docg3: the device
121562306a36Sopenharmony_ci * @to: the offset from first block and first page, in bytes, aligned on page
121662306a36Sopenharmony_ci *      size
121762306a36Sopenharmony_ci * @buf: buffer to get bytes from
121862306a36Sopenharmony_ci * @oob: buffer to get out of band bytes from (can be NULL if no OOB should be
121962306a36Sopenharmony_ci *       written)
122062306a36Sopenharmony_ci * @autoecc: if 0, all 16 bytes from OOB are taken, regardless of HW Hamming or
122162306a36Sopenharmony_ci *           BCH computations. If 1, only bytes 0-7 and byte 15 are taken,
122262306a36Sopenharmony_ci *           remaining ones are filled with hardware Hamming and BCH
122362306a36Sopenharmony_ci *           computations. Its value is not meaningfull is oob == NULL.
122462306a36Sopenharmony_ci *
122562306a36Sopenharmony_ci * Write one full page (ie. 1 page split on two planes), of 512 bytes, with the
122662306a36Sopenharmony_ci * OOB data. The OOB ECC is automatically computed by the hardware Hamming and
122762306a36Sopenharmony_ci * BCH generator if autoecc is not null.
122862306a36Sopenharmony_ci *
122962306a36Sopenharmony_ci * Returns 0 if write successful, -EIO if write error, -EAGAIN if timeout
123062306a36Sopenharmony_ci */
123162306a36Sopenharmony_cistatic int doc_write_page(struct docg3 *docg3, loff_t to, const u_char *buf,
123262306a36Sopenharmony_ci			  const u_char *oob, int autoecc)
123362306a36Sopenharmony_ci{
123462306a36Sopenharmony_ci	int block0, block1, page, ret, ofs = 0;
123562306a36Sopenharmony_ci	u8 hwecc[DOC_ECC_BCH_SIZE], hamming;
123662306a36Sopenharmony_ci
123762306a36Sopenharmony_ci	doc_dbg("doc_write_page(to=%lld)\n", to);
123862306a36Sopenharmony_ci	calc_block_sector(to, &block0, &block1, &page, &ofs, docg3->reliable);
123962306a36Sopenharmony_ci
124062306a36Sopenharmony_ci	doc_set_device_id(docg3, docg3->device_id);
124162306a36Sopenharmony_ci	ret = doc_reset_seq(docg3);
124262306a36Sopenharmony_ci	if (ret)
124362306a36Sopenharmony_ci		goto err;
124462306a36Sopenharmony_ci
124562306a36Sopenharmony_ci	/* Program the flash address block and page */
124662306a36Sopenharmony_ci	ret = doc_write_seek(docg3, block0, block1, page, ofs);
124762306a36Sopenharmony_ci	if (ret)
124862306a36Sopenharmony_ci		goto err;
124962306a36Sopenharmony_ci
125062306a36Sopenharmony_ci	doc_write_page_ecc_init(docg3, DOC_ECC_BCH_TOTAL_BYTES);
125162306a36Sopenharmony_ci	doc_delay(docg3, 2);
125262306a36Sopenharmony_ci	doc_write_page_putbytes(docg3, DOC_LAYOUT_PAGE_SIZE, buf);
125362306a36Sopenharmony_ci
125462306a36Sopenharmony_ci	if (oob && autoecc) {
125562306a36Sopenharmony_ci		doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_PAGEINFO_SZ, oob);
125662306a36Sopenharmony_ci		doc_delay(docg3, 2);
125762306a36Sopenharmony_ci		oob += DOC_LAYOUT_OOB_UNUSED_OFS;
125862306a36Sopenharmony_ci
125962306a36Sopenharmony_ci		hamming = doc_register_readb(docg3, DOC_HAMMINGPARITY);
126062306a36Sopenharmony_ci		doc_delay(docg3, 2);
126162306a36Sopenharmony_ci		doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_HAMMING_SZ,
126262306a36Sopenharmony_ci					&hamming);
126362306a36Sopenharmony_ci		doc_delay(docg3, 2);
126462306a36Sopenharmony_ci
126562306a36Sopenharmony_ci		doc_get_bch_hw_ecc(docg3, hwecc);
126662306a36Sopenharmony_ci		doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_BCH_SZ, hwecc);
126762306a36Sopenharmony_ci		doc_delay(docg3, 2);
126862306a36Sopenharmony_ci
126962306a36Sopenharmony_ci		doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_UNUSED_SZ, oob);
127062306a36Sopenharmony_ci	}
127162306a36Sopenharmony_ci	if (oob && !autoecc)
127262306a36Sopenharmony_ci		doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_SIZE, oob);
127362306a36Sopenharmony_ci
127462306a36Sopenharmony_ci	doc_delay(docg3, 2);
127562306a36Sopenharmony_ci	doc_page_finish(docg3);
127662306a36Sopenharmony_ci	doc_delay(docg3, 2);
127762306a36Sopenharmony_ci	doc_flash_command(docg3, DOC_CMD_PROG_CYCLE2);
127862306a36Sopenharmony_ci	doc_delay(docg3, 2);
127962306a36Sopenharmony_ci
128062306a36Sopenharmony_ci	/*
128162306a36Sopenharmony_ci	 * The wait status will perform another doc_page_finish() call, but that
128262306a36Sopenharmony_ci	 * seems to please the docg3, so leave it.
128362306a36Sopenharmony_ci	 */
128462306a36Sopenharmony_ci	ret = doc_write_erase_wait_status(docg3);
128562306a36Sopenharmony_ci	return ret;
128662306a36Sopenharmony_cierr:
128762306a36Sopenharmony_ci	doc_read_page_finish(docg3);
128862306a36Sopenharmony_ci	return ret;
128962306a36Sopenharmony_ci}
129062306a36Sopenharmony_ci
129162306a36Sopenharmony_ci/**
129262306a36Sopenharmony_ci * doc_guess_autoecc - Guess autoecc mode from mbd_oob_ops
129362306a36Sopenharmony_ci * @ops: the oob operations
129462306a36Sopenharmony_ci *
129562306a36Sopenharmony_ci * Returns 0 or 1 if success, -EINVAL if invalid oob mode
129662306a36Sopenharmony_ci */
129762306a36Sopenharmony_cistatic int doc_guess_autoecc(struct mtd_oob_ops *ops)
129862306a36Sopenharmony_ci{
129962306a36Sopenharmony_ci	int autoecc;
130062306a36Sopenharmony_ci
130162306a36Sopenharmony_ci	switch (ops->mode) {
130262306a36Sopenharmony_ci	case MTD_OPS_PLACE_OOB:
130362306a36Sopenharmony_ci	case MTD_OPS_AUTO_OOB:
130462306a36Sopenharmony_ci		autoecc = 1;
130562306a36Sopenharmony_ci		break;
130662306a36Sopenharmony_ci	case MTD_OPS_RAW:
130762306a36Sopenharmony_ci		autoecc = 0;
130862306a36Sopenharmony_ci		break;
130962306a36Sopenharmony_ci	default:
131062306a36Sopenharmony_ci		autoecc = -EINVAL;
131162306a36Sopenharmony_ci	}
131262306a36Sopenharmony_ci	return autoecc;
131362306a36Sopenharmony_ci}
131462306a36Sopenharmony_ci
131562306a36Sopenharmony_ci/**
131662306a36Sopenharmony_ci * doc_fill_autooob - Fill a 16 bytes OOB from 8 non-ECC bytes
131762306a36Sopenharmony_ci * @dst: the target 16 bytes OOB buffer
131862306a36Sopenharmony_ci * @oobsrc: the source 8 bytes non-ECC OOB buffer
131962306a36Sopenharmony_ci *
132062306a36Sopenharmony_ci */
132162306a36Sopenharmony_cistatic void doc_fill_autooob(u8 *dst, u8 *oobsrc)
132262306a36Sopenharmony_ci{
132362306a36Sopenharmony_ci	memcpy(dst, oobsrc, DOC_LAYOUT_OOB_PAGEINFO_SZ);
132462306a36Sopenharmony_ci	dst[DOC_LAYOUT_OOB_UNUSED_OFS] = oobsrc[DOC_LAYOUT_OOB_PAGEINFO_SZ];
132562306a36Sopenharmony_ci}
132662306a36Sopenharmony_ci
132762306a36Sopenharmony_ci/**
132862306a36Sopenharmony_ci * doc_backup_oob - Backup OOB into docg3 structure
132962306a36Sopenharmony_ci * @docg3: the device
133062306a36Sopenharmony_ci * @to: the page offset in the chip
133162306a36Sopenharmony_ci * @ops: the OOB size and buffer
133262306a36Sopenharmony_ci *
133362306a36Sopenharmony_ci * As the docg3 should write a page with its OOB in one pass, and some userland
133462306a36Sopenharmony_ci * applications do write_oob() to setup the OOB and then write(), store the OOB
133562306a36Sopenharmony_ci * into a temporary storage. This is very dangerous, as 2 concurrent
133662306a36Sopenharmony_ci * applications could store an OOB, and then write their pages (which will
133762306a36Sopenharmony_ci * result into one having its OOB corrupted).
133862306a36Sopenharmony_ci *
133962306a36Sopenharmony_ci * The only reliable way would be for userland to call doc_write_oob() with both
134062306a36Sopenharmony_ci * the page data _and_ the OOB area.
134162306a36Sopenharmony_ci *
134262306a36Sopenharmony_ci * Returns 0 if success, -EINVAL if ops content invalid
134362306a36Sopenharmony_ci */
134462306a36Sopenharmony_cistatic int doc_backup_oob(struct docg3 *docg3, loff_t to,
134562306a36Sopenharmony_ci			  struct mtd_oob_ops *ops)
134662306a36Sopenharmony_ci{
134762306a36Sopenharmony_ci	int ooblen = ops->ooblen, autoecc;
134862306a36Sopenharmony_ci
134962306a36Sopenharmony_ci	if (ooblen != DOC_LAYOUT_OOB_SIZE)
135062306a36Sopenharmony_ci		return -EINVAL;
135162306a36Sopenharmony_ci	autoecc = doc_guess_autoecc(ops);
135262306a36Sopenharmony_ci	if (autoecc < 0)
135362306a36Sopenharmony_ci		return autoecc;
135462306a36Sopenharmony_ci
135562306a36Sopenharmony_ci	docg3->oob_write_ofs = to;
135662306a36Sopenharmony_ci	docg3->oob_autoecc = autoecc;
135762306a36Sopenharmony_ci	if (ops->mode == MTD_OPS_AUTO_OOB) {
135862306a36Sopenharmony_ci		doc_fill_autooob(docg3->oob_write_buf, ops->oobbuf);
135962306a36Sopenharmony_ci		ops->oobretlen = 8;
136062306a36Sopenharmony_ci	} else {
136162306a36Sopenharmony_ci		memcpy(docg3->oob_write_buf, ops->oobbuf, DOC_LAYOUT_OOB_SIZE);
136262306a36Sopenharmony_ci		ops->oobretlen = DOC_LAYOUT_OOB_SIZE;
136362306a36Sopenharmony_ci	}
136462306a36Sopenharmony_ci	return 0;
136562306a36Sopenharmony_ci}
136662306a36Sopenharmony_ci
136762306a36Sopenharmony_ci/**
136862306a36Sopenharmony_ci * doc_write_oob - Write out of band bytes to flash
136962306a36Sopenharmony_ci * @mtd: the device
137062306a36Sopenharmony_ci * @ofs: the offset from first block and first page, in bytes, aligned on page
137162306a36Sopenharmony_ci *       size
137262306a36Sopenharmony_ci * @ops: the mtd oob structure
137362306a36Sopenharmony_ci *
137462306a36Sopenharmony_ci * Either write OOB data into a temporary buffer, for the subsequent write
137562306a36Sopenharmony_ci * page. The provided OOB should be 16 bytes long. If a data buffer is provided
137662306a36Sopenharmony_ci * as well, issue the page write.
137762306a36Sopenharmony_ci * Or provide data without OOB, and then a all zeroed OOB will be used (ECC will
137862306a36Sopenharmony_ci * still be filled in if asked for).
137962306a36Sopenharmony_ci *
138062306a36Sopenharmony_ci * Returns 0 is successful, EINVAL if length is not 14 bytes
138162306a36Sopenharmony_ci */
138262306a36Sopenharmony_cistatic int doc_write_oob(struct mtd_info *mtd, loff_t ofs,
138362306a36Sopenharmony_ci			 struct mtd_oob_ops *ops)
138462306a36Sopenharmony_ci{
138562306a36Sopenharmony_ci	struct docg3 *docg3 = mtd->priv;
138662306a36Sopenharmony_ci	int ret, autoecc, oobdelta;
138762306a36Sopenharmony_ci	u8 *oobbuf = ops->oobbuf;
138862306a36Sopenharmony_ci	u8 *buf = ops->datbuf;
138962306a36Sopenharmony_ci	size_t len, ooblen;
139062306a36Sopenharmony_ci	u8 oob[DOC_LAYOUT_OOB_SIZE];
139162306a36Sopenharmony_ci
139262306a36Sopenharmony_ci	if (buf)
139362306a36Sopenharmony_ci		len = ops->len;
139462306a36Sopenharmony_ci	else
139562306a36Sopenharmony_ci		len = 0;
139662306a36Sopenharmony_ci	if (oobbuf)
139762306a36Sopenharmony_ci		ooblen = ops->ooblen;
139862306a36Sopenharmony_ci	else
139962306a36Sopenharmony_ci		ooblen = 0;
140062306a36Sopenharmony_ci
140162306a36Sopenharmony_ci	if (oobbuf && ops->mode == MTD_OPS_PLACE_OOB)
140262306a36Sopenharmony_ci		oobbuf += ops->ooboffs;
140362306a36Sopenharmony_ci
140462306a36Sopenharmony_ci	doc_dbg("doc_write_oob(from=%lld, mode=%d, data=(%p:%zu), oob=(%p:%zu))\n",
140562306a36Sopenharmony_ci		ofs, ops->mode, buf, len, oobbuf, ooblen);
140662306a36Sopenharmony_ci	switch (ops->mode) {
140762306a36Sopenharmony_ci	case MTD_OPS_PLACE_OOB:
140862306a36Sopenharmony_ci	case MTD_OPS_RAW:
140962306a36Sopenharmony_ci		oobdelta = mtd->oobsize;
141062306a36Sopenharmony_ci		break;
141162306a36Sopenharmony_ci	case MTD_OPS_AUTO_OOB:
141262306a36Sopenharmony_ci		oobdelta = mtd->oobavail;
141362306a36Sopenharmony_ci		break;
141462306a36Sopenharmony_ci	default:
141562306a36Sopenharmony_ci		return -EINVAL;
141662306a36Sopenharmony_ci	}
141762306a36Sopenharmony_ci	if ((len % DOC_LAYOUT_PAGE_SIZE) || (ooblen % oobdelta) ||
141862306a36Sopenharmony_ci	    (ofs % DOC_LAYOUT_PAGE_SIZE))
141962306a36Sopenharmony_ci		return -EINVAL;
142062306a36Sopenharmony_ci	if (len && ooblen &&
142162306a36Sopenharmony_ci	    (len / DOC_LAYOUT_PAGE_SIZE) != (ooblen / oobdelta))
142262306a36Sopenharmony_ci		return -EINVAL;
142362306a36Sopenharmony_ci
142462306a36Sopenharmony_ci	ops->oobretlen = 0;
142562306a36Sopenharmony_ci	ops->retlen = 0;
142662306a36Sopenharmony_ci	ret = 0;
142762306a36Sopenharmony_ci	if (len == 0 && ooblen == 0)
142862306a36Sopenharmony_ci		return -EINVAL;
142962306a36Sopenharmony_ci	if (len == 0 && ooblen > 0)
143062306a36Sopenharmony_ci		return doc_backup_oob(docg3, ofs, ops);
143162306a36Sopenharmony_ci
143262306a36Sopenharmony_ci	autoecc = doc_guess_autoecc(ops);
143362306a36Sopenharmony_ci	if (autoecc < 0)
143462306a36Sopenharmony_ci		return autoecc;
143562306a36Sopenharmony_ci
143662306a36Sopenharmony_ci	mutex_lock(&docg3->cascade->lock);
143762306a36Sopenharmony_ci	while (!ret && len > 0) {
143862306a36Sopenharmony_ci		memset(oob, 0, sizeof(oob));
143962306a36Sopenharmony_ci		if (ofs == docg3->oob_write_ofs)
144062306a36Sopenharmony_ci			memcpy(oob, docg3->oob_write_buf, DOC_LAYOUT_OOB_SIZE);
144162306a36Sopenharmony_ci		else if (ooblen > 0 && ops->mode == MTD_OPS_AUTO_OOB)
144262306a36Sopenharmony_ci			doc_fill_autooob(oob, oobbuf);
144362306a36Sopenharmony_ci		else if (ooblen > 0)
144462306a36Sopenharmony_ci			memcpy(oob, oobbuf, DOC_LAYOUT_OOB_SIZE);
144562306a36Sopenharmony_ci		ret = doc_write_page(docg3, ofs, buf, oob, autoecc);
144662306a36Sopenharmony_ci
144762306a36Sopenharmony_ci		ofs += DOC_LAYOUT_PAGE_SIZE;
144862306a36Sopenharmony_ci		len -= DOC_LAYOUT_PAGE_SIZE;
144962306a36Sopenharmony_ci		buf += DOC_LAYOUT_PAGE_SIZE;
145062306a36Sopenharmony_ci		if (ooblen) {
145162306a36Sopenharmony_ci			oobbuf += oobdelta;
145262306a36Sopenharmony_ci			ooblen -= oobdelta;
145362306a36Sopenharmony_ci			ops->oobretlen += oobdelta;
145462306a36Sopenharmony_ci		}
145562306a36Sopenharmony_ci		ops->retlen += DOC_LAYOUT_PAGE_SIZE;
145662306a36Sopenharmony_ci	}
145762306a36Sopenharmony_ci
145862306a36Sopenharmony_ci	doc_set_device_id(docg3, 0);
145962306a36Sopenharmony_ci	mutex_unlock(&docg3->cascade->lock);
146062306a36Sopenharmony_ci	return ret;
146162306a36Sopenharmony_ci}
146262306a36Sopenharmony_ci
146362306a36Sopenharmony_cistatic struct docg3 *sysfs_dev2docg3(struct device *dev,
146462306a36Sopenharmony_ci				     struct device_attribute *attr)
146562306a36Sopenharmony_ci{
146662306a36Sopenharmony_ci	int floor;
146762306a36Sopenharmony_ci	struct mtd_info **docg3_floors = dev_get_drvdata(dev);
146862306a36Sopenharmony_ci
146962306a36Sopenharmony_ci	floor = attr->attr.name[1] - '0';
147062306a36Sopenharmony_ci	if (floor < 0 || floor >= DOC_MAX_NBFLOORS)
147162306a36Sopenharmony_ci		return NULL;
147262306a36Sopenharmony_ci	else
147362306a36Sopenharmony_ci		return docg3_floors[floor]->priv;
147462306a36Sopenharmony_ci}
147562306a36Sopenharmony_ci
147662306a36Sopenharmony_cistatic ssize_t dps0_is_key_locked(struct device *dev,
147762306a36Sopenharmony_ci				  struct device_attribute *attr, char *buf)
147862306a36Sopenharmony_ci{
147962306a36Sopenharmony_ci	struct docg3 *docg3 = sysfs_dev2docg3(dev, attr);
148062306a36Sopenharmony_ci	int dps0;
148162306a36Sopenharmony_ci
148262306a36Sopenharmony_ci	mutex_lock(&docg3->cascade->lock);
148362306a36Sopenharmony_ci	doc_set_device_id(docg3, docg3->device_id);
148462306a36Sopenharmony_ci	dps0 = doc_register_readb(docg3, DOC_DPS0_STATUS);
148562306a36Sopenharmony_ci	doc_set_device_id(docg3, 0);
148662306a36Sopenharmony_ci	mutex_unlock(&docg3->cascade->lock);
148762306a36Sopenharmony_ci
148862306a36Sopenharmony_ci	return sprintf(buf, "%d\n", !(dps0 & DOC_DPS_KEY_OK));
148962306a36Sopenharmony_ci}
149062306a36Sopenharmony_ci
149162306a36Sopenharmony_cistatic ssize_t dps1_is_key_locked(struct device *dev,
149262306a36Sopenharmony_ci				  struct device_attribute *attr, char *buf)
149362306a36Sopenharmony_ci{
149462306a36Sopenharmony_ci	struct docg3 *docg3 = sysfs_dev2docg3(dev, attr);
149562306a36Sopenharmony_ci	int dps1;
149662306a36Sopenharmony_ci
149762306a36Sopenharmony_ci	mutex_lock(&docg3->cascade->lock);
149862306a36Sopenharmony_ci	doc_set_device_id(docg3, docg3->device_id);
149962306a36Sopenharmony_ci	dps1 = doc_register_readb(docg3, DOC_DPS1_STATUS);
150062306a36Sopenharmony_ci	doc_set_device_id(docg3, 0);
150162306a36Sopenharmony_ci	mutex_unlock(&docg3->cascade->lock);
150262306a36Sopenharmony_ci
150362306a36Sopenharmony_ci	return sprintf(buf, "%d\n", !(dps1 & DOC_DPS_KEY_OK));
150462306a36Sopenharmony_ci}
150562306a36Sopenharmony_ci
150662306a36Sopenharmony_cistatic ssize_t dps0_insert_key(struct device *dev,
150762306a36Sopenharmony_ci			       struct device_attribute *attr,
150862306a36Sopenharmony_ci			       const char *buf, size_t count)
150962306a36Sopenharmony_ci{
151062306a36Sopenharmony_ci	struct docg3 *docg3 = sysfs_dev2docg3(dev, attr);
151162306a36Sopenharmony_ci	int i;
151262306a36Sopenharmony_ci
151362306a36Sopenharmony_ci	if (count != DOC_LAYOUT_DPS_KEY_LENGTH)
151462306a36Sopenharmony_ci		return -EINVAL;
151562306a36Sopenharmony_ci
151662306a36Sopenharmony_ci	mutex_lock(&docg3->cascade->lock);
151762306a36Sopenharmony_ci	doc_set_device_id(docg3, docg3->device_id);
151862306a36Sopenharmony_ci	for (i = 0; i < DOC_LAYOUT_DPS_KEY_LENGTH; i++)
151962306a36Sopenharmony_ci		doc_writeb(docg3, buf[i], DOC_DPS0_KEY);
152062306a36Sopenharmony_ci	doc_set_device_id(docg3, 0);
152162306a36Sopenharmony_ci	mutex_unlock(&docg3->cascade->lock);
152262306a36Sopenharmony_ci	return count;
152362306a36Sopenharmony_ci}
152462306a36Sopenharmony_ci
152562306a36Sopenharmony_cistatic ssize_t dps1_insert_key(struct device *dev,
152662306a36Sopenharmony_ci			       struct device_attribute *attr,
152762306a36Sopenharmony_ci			       const char *buf, size_t count)
152862306a36Sopenharmony_ci{
152962306a36Sopenharmony_ci	struct docg3 *docg3 = sysfs_dev2docg3(dev, attr);
153062306a36Sopenharmony_ci	int i;
153162306a36Sopenharmony_ci
153262306a36Sopenharmony_ci	if (count != DOC_LAYOUT_DPS_KEY_LENGTH)
153362306a36Sopenharmony_ci		return -EINVAL;
153462306a36Sopenharmony_ci
153562306a36Sopenharmony_ci	mutex_lock(&docg3->cascade->lock);
153662306a36Sopenharmony_ci	doc_set_device_id(docg3, docg3->device_id);
153762306a36Sopenharmony_ci	for (i = 0; i < DOC_LAYOUT_DPS_KEY_LENGTH; i++)
153862306a36Sopenharmony_ci		doc_writeb(docg3, buf[i], DOC_DPS1_KEY);
153962306a36Sopenharmony_ci	doc_set_device_id(docg3, 0);
154062306a36Sopenharmony_ci	mutex_unlock(&docg3->cascade->lock);
154162306a36Sopenharmony_ci	return count;
154262306a36Sopenharmony_ci}
154362306a36Sopenharmony_ci
154462306a36Sopenharmony_ci#define FLOOR_SYSFS(id) { \
154562306a36Sopenharmony_ci	__ATTR(f##id##_dps0_is_keylocked, S_IRUGO, dps0_is_key_locked, NULL), \
154662306a36Sopenharmony_ci	__ATTR(f##id##_dps1_is_keylocked, S_IRUGO, dps1_is_key_locked, NULL), \
154762306a36Sopenharmony_ci	__ATTR(f##id##_dps0_protection_key, S_IWUSR|S_IWGRP, NULL, dps0_insert_key), \
154862306a36Sopenharmony_ci	__ATTR(f##id##_dps1_protection_key, S_IWUSR|S_IWGRP, NULL, dps1_insert_key), \
154962306a36Sopenharmony_ci}
155062306a36Sopenharmony_ci
155162306a36Sopenharmony_cistatic struct device_attribute doc_sys_attrs[DOC_MAX_NBFLOORS][4] = {
155262306a36Sopenharmony_ci	FLOOR_SYSFS(0), FLOOR_SYSFS(1), FLOOR_SYSFS(2), FLOOR_SYSFS(3)
155362306a36Sopenharmony_ci};
155462306a36Sopenharmony_ci
155562306a36Sopenharmony_cistatic int doc_register_sysfs(struct platform_device *pdev,
155662306a36Sopenharmony_ci			      struct docg3_cascade *cascade)
155762306a36Sopenharmony_ci{
155862306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
155962306a36Sopenharmony_ci	int floor;
156062306a36Sopenharmony_ci	int ret;
156162306a36Sopenharmony_ci	int i;
156262306a36Sopenharmony_ci
156362306a36Sopenharmony_ci	for (floor = 0;
156462306a36Sopenharmony_ci	     floor < DOC_MAX_NBFLOORS && cascade->floors[floor];
156562306a36Sopenharmony_ci	     floor++) {
156662306a36Sopenharmony_ci		for (i = 0; i < 4; i++) {
156762306a36Sopenharmony_ci			ret = device_create_file(dev, &doc_sys_attrs[floor][i]);
156862306a36Sopenharmony_ci			if (ret)
156962306a36Sopenharmony_ci				goto remove_files;
157062306a36Sopenharmony_ci		}
157162306a36Sopenharmony_ci	}
157262306a36Sopenharmony_ci
157362306a36Sopenharmony_ci	return 0;
157462306a36Sopenharmony_ci
157562306a36Sopenharmony_ciremove_files:
157662306a36Sopenharmony_ci	do {
157762306a36Sopenharmony_ci		while (--i >= 0)
157862306a36Sopenharmony_ci			device_remove_file(dev, &doc_sys_attrs[floor][i]);
157962306a36Sopenharmony_ci		i = 4;
158062306a36Sopenharmony_ci	} while (--floor >= 0);
158162306a36Sopenharmony_ci
158262306a36Sopenharmony_ci	return ret;
158362306a36Sopenharmony_ci}
158462306a36Sopenharmony_ci
158562306a36Sopenharmony_cistatic void doc_unregister_sysfs(struct platform_device *pdev,
158662306a36Sopenharmony_ci				 struct docg3_cascade *cascade)
158762306a36Sopenharmony_ci{
158862306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
158962306a36Sopenharmony_ci	int floor, i;
159062306a36Sopenharmony_ci
159162306a36Sopenharmony_ci	for (floor = 0; floor < DOC_MAX_NBFLOORS && cascade->floors[floor];
159262306a36Sopenharmony_ci	     floor++)
159362306a36Sopenharmony_ci		for (i = 0; i < 4; i++)
159462306a36Sopenharmony_ci			device_remove_file(dev, &doc_sys_attrs[floor][i]);
159562306a36Sopenharmony_ci}
159662306a36Sopenharmony_ci
159762306a36Sopenharmony_ci/*
159862306a36Sopenharmony_ci * Debug sysfs entries
159962306a36Sopenharmony_ci */
160062306a36Sopenharmony_cistatic int flashcontrol_show(struct seq_file *s, void *p)
160162306a36Sopenharmony_ci{
160262306a36Sopenharmony_ci	struct docg3 *docg3 = s->private;
160362306a36Sopenharmony_ci
160462306a36Sopenharmony_ci	u8 fctrl;
160562306a36Sopenharmony_ci
160662306a36Sopenharmony_ci	mutex_lock(&docg3->cascade->lock);
160762306a36Sopenharmony_ci	fctrl = doc_register_readb(docg3, DOC_FLASHCONTROL);
160862306a36Sopenharmony_ci	mutex_unlock(&docg3->cascade->lock);
160962306a36Sopenharmony_ci
161062306a36Sopenharmony_ci	seq_printf(s, "FlashControl : 0x%02x (%s,CE# %s,%s,%s,flash %s)\n",
161162306a36Sopenharmony_ci		   fctrl,
161262306a36Sopenharmony_ci		   fctrl & DOC_CTRL_VIOLATION ? "protocol violation" : "-",
161362306a36Sopenharmony_ci		   fctrl & DOC_CTRL_CE ? "active" : "inactive",
161462306a36Sopenharmony_ci		   fctrl & DOC_CTRL_PROTECTION_ERROR ? "protection error" : "-",
161562306a36Sopenharmony_ci		   fctrl & DOC_CTRL_SEQUENCE_ERROR ? "sequence error" : "-",
161662306a36Sopenharmony_ci		   fctrl & DOC_CTRL_FLASHREADY ? "ready" : "not ready");
161762306a36Sopenharmony_ci
161862306a36Sopenharmony_ci	return 0;
161962306a36Sopenharmony_ci}
162062306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(flashcontrol);
162162306a36Sopenharmony_ci
162262306a36Sopenharmony_cistatic int asic_mode_show(struct seq_file *s, void *p)
162362306a36Sopenharmony_ci{
162462306a36Sopenharmony_ci	struct docg3 *docg3 = s->private;
162562306a36Sopenharmony_ci
162662306a36Sopenharmony_ci	int pctrl, mode;
162762306a36Sopenharmony_ci
162862306a36Sopenharmony_ci	mutex_lock(&docg3->cascade->lock);
162962306a36Sopenharmony_ci	pctrl = doc_register_readb(docg3, DOC_ASICMODE);
163062306a36Sopenharmony_ci	mode = pctrl & 0x03;
163162306a36Sopenharmony_ci	mutex_unlock(&docg3->cascade->lock);
163262306a36Sopenharmony_ci
163362306a36Sopenharmony_ci	seq_printf(s,
163462306a36Sopenharmony_ci		   "%04x : RAM_WE=%d,RSTIN_RESET=%d,BDETCT_RESET=%d,WRITE_ENABLE=%d,POWERDOWN=%d,MODE=%d%d (",
163562306a36Sopenharmony_ci		   pctrl,
163662306a36Sopenharmony_ci		   pctrl & DOC_ASICMODE_RAM_WE ? 1 : 0,
163762306a36Sopenharmony_ci		   pctrl & DOC_ASICMODE_RSTIN_RESET ? 1 : 0,
163862306a36Sopenharmony_ci		   pctrl & DOC_ASICMODE_BDETCT_RESET ? 1 : 0,
163962306a36Sopenharmony_ci		   pctrl & DOC_ASICMODE_MDWREN ? 1 : 0,
164062306a36Sopenharmony_ci		   pctrl & DOC_ASICMODE_POWERDOWN ? 1 : 0,
164162306a36Sopenharmony_ci		   mode >> 1, mode & 0x1);
164262306a36Sopenharmony_ci
164362306a36Sopenharmony_ci	switch (mode) {
164462306a36Sopenharmony_ci	case DOC_ASICMODE_RESET:
164562306a36Sopenharmony_ci		seq_puts(s, "reset");
164662306a36Sopenharmony_ci		break;
164762306a36Sopenharmony_ci	case DOC_ASICMODE_NORMAL:
164862306a36Sopenharmony_ci		seq_puts(s, "normal");
164962306a36Sopenharmony_ci		break;
165062306a36Sopenharmony_ci	case DOC_ASICMODE_POWERDOWN:
165162306a36Sopenharmony_ci		seq_puts(s, "powerdown");
165262306a36Sopenharmony_ci		break;
165362306a36Sopenharmony_ci	}
165462306a36Sopenharmony_ci	seq_puts(s, ")\n");
165562306a36Sopenharmony_ci	return 0;
165662306a36Sopenharmony_ci}
165762306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(asic_mode);
165862306a36Sopenharmony_ci
165962306a36Sopenharmony_cistatic int device_id_show(struct seq_file *s, void *p)
166062306a36Sopenharmony_ci{
166162306a36Sopenharmony_ci	struct docg3 *docg3 = s->private;
166262306a36Sopenharmony_ci	int id;
166362306a36Sopenharmony_ci
166462306a36Sopenharmony_ci	mutex_lock(&docg3->cascade->lock);
166562306a36Sopenharmony_ci	id = doc_register_readb(docg3, DOC_DEVICESELECT);
166662306a36Sopenharmony_ci	mutex_unlock(&docg3->cascade->lock);
166762306a36Sopenharmony_ci
166862306a36Sopenharmony_ci	seq_printf(s, "DeviceId = %d\n", id);
166962306a36Sopenharmony_ci	return 0;
167062306a36Sopenharmony_ci}
167162306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(device_id);
167262306a36Sopenharmony_ci
167362306a36Sopenharmony_cistatic int protection_show(struct seq_file *s, void *p)
167462306a36Sopenharmony_ci{
167562306a36Sopenharmony_ci	struct docg3 *docg3 = s->private;
167662306a36Sopenharmony_ci	int protect, dps0, dps0_low, dps0_high, dps1, dps1_low, dps1_high;
167762306a36Sopenharmony_ci
167862306a36Sopenharmony_ci	mutex_lock(&docg3->cascade->lock);
167962306a36Sopenharmony_ci	protect = doc_register_readb(docg3, DOC_PROTECTION);
168062306a36Sopenharmony_ci	dps0 = doc_register_readb(docg3, DOC_DPS0_STATUS);
168162306a36Sopenharmony_ci	dps0_low = doc_register_readw(docg3, DOC_DPS0_ADDRLOW);
168262306a36Sopenharmony_ci	dps0_high = doc_register_readw(docg3, DOC_DPS0_ADDRHIGH);
168362306a36Sopenharmony_ci	dps1 = doc_register_readb(docg3, DOC_DPS1_STATUS);
168462306a36Sopenharmony_ci	dps1_low = doc_register_readw(docg3, DOC_DPS1_ADDRLOW);
168562306a36Sopenharmony_ci	dps1_high = doc_register_readw(docg3, DOC_DPS1_ADDRHIGH);
168662306a36Sopenharmony_ci	mutex_unlock(&docg3->cascade->lock);
168762306a36Sopenharmony_ci
168862306a36Sopenharmony_ci	seq_printf(s, "Protection = 0x%02x (", protect);
168962306a36Sopenharmony_ci	if (protect & DOC_PROTECT_FOUNDRY_OTP_LOCK)
169062306a36Sopenharmony_ci		seq_puts(s, "FOUNDRY_OTP_LOCK,");
169162306a36Sopenharmony_ci	if (protect & DOC_PROTECT_CUSTOMER_OTP_LOCK)
169262306a36Sopenharmony_ci		seq_puts(s, "CUSTOMER_OTP_LOCK,");
169362306a36Sopenharmony_ci	if (protect & DOC_PROTECT_LOCK_INPUT)
169462306a36Sopenharmony_ci		seq_puts(s, "LOCK_INPUT,");
169562306a36Sopenharmony_ci	if (protect & DOC_PROTECT_STICKY_LOCK)
169662306a36Sopenharmony_ci		seq_puts(s, "STICKY_LOCK,");
169762306a36Sopenharmony_ci	if (protect & DOC_PROTECT_PROTECTION_ENABLED)
169862306a36Sopenharmony_ci		seq_puts(s, "PROTECTION ON,");
169962306a36Sopenharmony_ci	if (protect & DOC_PROTECT_IPL_DOWNLOAD_LOCK)
170062306a36Sopenharmony_ci		seq_puts(s, "IPL_DOWNLOAD_LOCK,");
170162306a36Sopenharmony_ci	if (protect & DOC_PROTECT_PROTECTION_ERROR)
170262306a36Sopenharmony_ci		seq_puts(s, "PROTECT_ERR,");
170362306a36Sopenharmony_ci	else
170462306a36Sopenharmony_ci		seq_puts(s, "NO_PROTECT_ERR");
170562306a36Sopenharmony_ci	seq_puts(s, ")\n");
170662306a36Sopenharmony_ci
170762306a36Sopenharmony_ci	seq_printf(s, "DPS0 = 0x%02x : Protected area [0x%x - 0x%x] : OTP=%d, READ=%d, WRITE=%d, HW_LOCK=%d, KEY_OK=%d\n",
170862306a36Sopenharmony_ci		   dps0, dps0_low, dps0_high,
170962306a36Sopenharmony_ci		   !!(dps0 & DOC_DPS_OTP_PROTECTED),
171062306a36Sopenharmony_ci		   !!(dps0 & DOC_DPS_READ_PROTECTED),
171162306a36Sopenharmony_ci		   !!(dps0 & DOC_DPS_WRITE_PROTECTED),
171262306a36Sopenharmony_ci		   !!(dps0 & DOC_DPS_HW_LOCK_ENABLED),
171362306a36Sopenharmony_ci		   !!(dps0 & DOC_DPS_KEY_OK));
171462306a36Sopenharmony_ci	seq_printf(s, "DPS1 = 0x%02x : Protected area [0x%x - 0x%x] : OTP=%d, READ=%d, WRITE=%d, HW_LOCK=%d, KEY_OK=%d\n",
171562306a36Sopenharmony_ci		   dps1, dps1_low, dps1_high,
171662306a36Sopenharmony_ci		   !!(dps1 & DOC_DPS_OTP_PROTECTED),
171762306a36Sopenharmony_ci		   !!(dps1 & DOC_DPS_READ_PROTECTED),
171862306a36Sopenharmony_ci		   !!(dps1 & DOC_DPS_WRITE_PROTECTED),
171962306a36Sopenharmony_ci		   !!(dps1 & DOC_DPS_HW_LOCK_ENABLED),
172062306a36Sopenharmony_ci		   !!(dps1 & DOC_DPS_KEY_OK));
172162306a36Sopenharmony_ci	return 0;
172262306a36Sopenharmony_ci}
172362306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(protection);
172462306a36Sopenharmony_ci
172562306a36Sopenharmony_cistatic void __init doc_dbg_register(struct mtd_info *floor)
172662306a36Sopenharmony_ci{
172762306a36Sopenharmony_ci	struct dentry *root = floor->dbg.dfs_dir;
172862306a36Sopenharmony_ci	struct docg3 *docg3 = floor->priv;
172962306a36Sopenharmony_ci
173062306a36Sopenharmony_ci	if (IS_ERR_OR_NULL(root)) {
173162306a36Sopenharmony_ci		if (IS_ENABLED(CONFIG_DEBUG_FS) &&
173262306a36Sopenharmony_ci		    !IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
173362306a36Sopenharmony_ci			dev_warn(floor->dev.parent,
173462306a36Sopenharmony_ci				 "CONFIG_MTD_PARTITIONED_MASTER must be enabled to expose debugfs stuff\n");
173562306a36Sopenharmony_ci		return;
173662306a36Sopenharmony_ci	}
173762306a36Sopenharmony_ci
173862306a36Sopenharmony_ci	debugfs_create_file("docg3_flashcontrol", S_IRUSR, root, docg3,
173962306a36Sopenharmony_ci			    &flashcontrol_fops);
174062306a36Sopenharmony_ci	debugfs_create_file("docg3_asic_mode", S_IRUSR, root, docg3,
174162306a36Sopenharmony_ci			    &asic_mode_fops);
174262306a36Sopenharmony_ci	debugfs_create_file("docg3_device_id", S_IRUSR, root, docg3,
174362306a36Sopenharmony_ci			    &device_id_fops);
174462306a36Sopenharmony_ci	debugfs_create_file("docg3_protection", S_IRUSR, root, docg3,
174562306a36Sopenharmony_ci			    &protection_fops);
174662306a36Sopenharmony_ci}
174762306a36Sopenharmony_ci
174862306a36Sopenharmony_ci/**
174962306a36Sopenharmony_ci * doc_set_driver_info - Fill the mtd_info structure and docg3 structure
175062306a36Sopenharmony_ci * @chip_id: The chip ID of the supported chip
175162306a36Sopenharmony_ci * @mtd: The structure to fill
175262306a36Sopenharmony_ci */
175362306a36Sopenharmony_cistatic int __init doc_set_driver_info(int chip_id, struct mtd_info *mtd)
175462306a36Sopenharmony_ci{
175562306a36Sopenharmony_ci	struct docg3 *docg3 = mtd->priv;
175662306a36Sopenharmony_ci	int cfg;
175762306a36Sopenharmony_ci
175862306a36Sopenharmony_ci	cfg = doc_register_readb(docg3, DOC_CONFIGURATION);
175962306a36Sopenharmony_ci	docg3->if_cfg = (cfg & DOC_CONF_IF_CFG ? 1 : 0);
176062306a36Sopenharmony_ci	docg3->reliable = reliable_mode;
176162306a36Sopenharmony_ci
176262306a36Sopenharmony_ci	switch (chip_id) {
176362306a36Sopenharmony_ci	case DOC_CHIPID_G3:
176462306a36Sopenharmony_ci		mtd->name = devm_kasprintf(docg3->dev, GFP_KERNEL, "docg3.%d",
176562306a36Sopenharmony_ci					   docg3->device_id);
176662306a36Sopenharmony_ci		if (!mtd->name)
176762306a36Sopenharmony_ci			return -ENOMEM;
176862306a36Sopenharmony_ci		docg3->max_block = 2047;
176962306a36Sopenharmony_ci		break;
177062306a36Sopenharmony_ci	}
177162306a36Sopenharmony_ci	mtd->type = MTD_NANDFLASH;
177262306a36Sopenharmony_ci	mtd->flags = MTD_CAP_NANDFLASH;
177362306a36Sopenharmony_ci	mtd->size = (docg3->max_block + 1) * DOC_LAYOUT_BLOCK_SIZE;
177462306a36Sopenharmony_ci	if (docg3->reliable == 2)
177562306a36Sopenharmony_ci		mtd->size /= 2;
177662306a36Sopenharmony_ci	mtd->erasesize = DOC_LAYOUT_BLOCK_SIZE * DOC_LAYOUT_NBPLANES;
177762306a36Sopenharmony_ci	if (docg3->reliable == 2)
177862306a36Sopenharmony_ci		mtd->erasesize /= 2;
177962306a36Sopenharmony_ci	mtd->writebufsize = mtd->writesize = DOC_LAYOUT_PAGE_SIZE;
178062306a36Sopenharmony_ci	mtd->oobsize = DOC_LAYOUT_OOB_SIZE;
178162306a36Sopenharmony_ci	mtd->_erase = doc_erase;
178262306a36Sopenharmony_ci	mtd->_read_oob = doc_read_oob;
178362306a36Sopenharmony_ci	mtd->_write_oob = doc_write_oob;
178462306a36Sopenharmony_ci	mtd->_block_isbad = doc_block_isbad;
178562306a36Sopenharmony_ci	mtd_set_ooblayout(mtd, &nand_ooblayout_docg3_ops);
178662306a36Sopenharmony_ci	mtd->oobavail = 8;
178762306a36Sopenharmony_ci	mtd->ecc_strength = DOC_ECC_BCH_T;
178862306a36Sopenharmony_ci
178962306a36Sopenharmony_ci	return 0;
179062306a36Sopenharmony_ci}
179162306a36Sopenharmony_ci
179262306a36Sopenharmony_ci/**
179362306a36Sopenharmony_ci * doc_probe_device - Check if a device is available
179462306a36Sopenharmony_ci * @cascade: the cascade of chips this devices will belong to
179562306a36Sopenharmony_ci * @floor: the floor of the probed device
179662306a36Sopenharmony_ci * @dev: the device
179762306a36Sopenharmony_ci *
179862306a36Sopenharmony_ci * Checks whether a device at the specified IO range, and floor is available.
179962306a36Sopenharmony_ci *
180062306a36Sopenharmony_ci * Returns a mtd_info struct if there is a device, ENODEV if none found, ENOMEM
180162306a36Sopenharmony_ci * if a memory allocation failed. If floor 0 is checked, a reset of the ASIC is
180262306a36Sopenharmony_ci * launched.
180362306a36Sopenharmony_ci */
180462306a36Sopenharmony_cistatic struct mtd_info * __init
180562306a36Sopenharmony_cidoc_probe_device(struct docg3_cascade *cascade, int floor, struct device *dev)
180662306a36Sopenharmony_ci{
180762306a36Sopenharmony_ci	int ret, bbt_nbpages;
180862306a36Sopenharmony_ci	u16 chip_id, chip_id_inv;
180962306a36Sopenharmony_ci	struct docg3 *docg3;
181062306a36Sopenharmony_ci	struct mtd_info *mtd;
181162306a36Sopenharmony_ci
181262306a36Sopenharmony_ci	ret = -ENOMEM;
181362306a36Sopenharmony_ci	docg3 = kzalloc(sizeof(struct docg3), GFP_KERNEL);
181462306a36Sopenharmony_ci	if (!docg3)
181562306a36Sopenharmony_ci		goto nomem1;
181662306a36Sopenharmony_ci	mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
181762306a36Sopenharmony_ci	if (!mtd)
181862306a36Sopenharmony_ci		goto nomem2;
181962306a36Sopenharmony_ci	mtd->priv = docg3;
182062306a36Sopenharmony_ci	mtd->dev.parent = dev;
182162306a36Sopenharmony_ci	bbt_nbpages = DIV_ROUND_UP(docg3->max_block + 1,
182262306a36Sopenharmony_ci				   8 * DOC_LAYOUT_PAGE_SIZE);
182362306a36Sopenharmony_ci	docg3->bbt = kcalloc(DOC_LAYOUT_PAGE_SIZE, bbt_nbpages, GFP_KERNEL);
182462306a36Sopenharmony_ci	if (!docg3->bbt)
182562306a36Sopenharmony_ci		goto nomem3;
182662306a36Sopenharmony_ci
182762306a36Sopenharmony_ci	docg3->dev = dev;
182862306a36Sopenharmony_ci	docg3->device_id = floor;
182962306a36Sopenharmony_ci	docg3->cascade = cascade;
183062306a36Sopenharmony_ci	doc_set_device_id(docg3, docg3->device_id);
183162306a36Sopenharmony_ci	if (!floor)
183262306a36Sopenharmony_ci		doc_set_asic_mode(docg3, DOC_ASICMODE_RESET);
183362306a36Sopenharmony_ci	doc_set_asic_mode(docg3, DOC_ASICMODE_NORMAL);
183462306a36Sopenharmony_ci
183562306a36Sopenharmony_ci	chip_id = doc_register_readw(docg3, DOC_CHIPID);
183662306a36Sopenharmony_ci	chip_id_inv = doc_register_readw(docg3, DOC_CHIPID_INV);
183762306a36Sopenharmony_ci
183862306a36Sopenharmony_ci	ret = 0;
183962306a36Sopenharmony_ci	if (chip_id != (u16)(~chip_id_inv)) {
184062306a36Sopenharmony_ci		goto nomem4;
184162306a36Sopenharmony_ci	}
184262306a36Sopenharmony_ci
184362306a36Sopenharmony_ci	switch (chip_id) {
184462306a36Sopenharmony_ci	case DOC_CHIPID_G3:
184562306a36Sopenharmony_ci		doc_info("Found a G3 DiskOnChip at addr %p, floor %d\n",
184662306a36Sopenharmony_ci			 docg3->cascade->base, floor);
184762306a36Sopenharmony_ci		break;
184862306a36Sopenharmony_ci	default:
184962306a36Sopenharmony_ci		doc_err("Chip id %04x is not a DiskOnChip G3 chip\n", chip_id);
185062306a36Sopenharmony_ci		goto nomem4;
185162306a36Sopenharmony_ci	}
185262306a36Sopenharmony_ci
185362306a36Sopenharmony_ci	ret = doc_set_driver_info(chip_id, mtd);
185462306a36Sopenharmony_ci	if (ret)
185562306a36Sopenharmony_ci		goto nomem4;
185662306a36Sopenharmony_ci
185762306a36Sopenharmony_ci	doc_hamming_ecc_init(docg3, DOC_LAYOUT_OOB_PAGEINFO_SZ);
185862306a36Sopenharmony_ci	doc_reload_bbt(docg3);
185962306a36Sopenharmony_ci	return mtd;
186062306a36Sopenharmony_ci
186162306a36Sopenharmony_cinomem4:
186262306a36Sopenharmony_ci	kfree(docg3->bbt);
186362306a36Sopenharmony_cinomem3:
186462306a36Sopenharmony_ci	kfree(mtd);
186562306a36Sopenharmony_cinomem2:
186662306a36Sopenharmony_ci	kfree(docg3);
186762306a36Sopenharmony_cinomem1:
186862306a36Sopenharmony_ci	return ret ? ERR_PTR(ret) : NULL;
186962306a36Sopenharmony_ci}
187062306a36Sopenharmony_ci
187162306a36Sopenharmony_ci/**
187262306a36Sopenharmony_ci * doc_release_device - Release a docg3 floor
187362306a36Sopenharmony_ci * @mtd: the device
187462306a36Sopenharmony_ci */
187562306a36Sopenharmony_cistatic void doc_release_device(struct mtd_info *mtd)
187662306a36Sopenharmony_ci{
187762306a36Sopenharmony_ci	struct docg3 *docg3 = mtd->priv;
187862306a36Sopenharmony_ci
187962306a36Sopenharmony_ci	mtd_device_unregister(mtd);
188062306a36Sopenharmony_ci	kfree(docg3->bbt);
188162306a36Sopenharmony_ci	kfree(docg3);
188262306a36Sopenharmony_ci	kfree(mtd);
188362306a36Sopenharmony_ci}
188462306a36Sopenharmony_ci
188562306a36Sopenharmony_ci/**
188662306a36Sopenharmony_ci * docg3_resume - Awakens docg3 floor
188762306a36Sopenharmony_ci * @pdev: platfrom device
188862306a36Sopenharmony_ci *
188962306a36Sopenharmony_ci * Returns 0 (always successful)
189062306a36Sopenharmony_ci */
189162306a36Sopenharmony_cistatic int docg3_resume(struct platform_device *pdev)
189262306a36Sopenharmony_ci{
189362306a36Sopenharmony_ci	int i;
189462306a36Sopenharmony_ci	struct docg3_cascade *cascade;
189562306a36Sopenharmony_ci	struct mtd_info **docg3_floors, *mtd;
189662306a36Sopenharmony_ci	struct docg3 *docg3;
189762306a36Sopenharmony_ci
189862306a36Sopenharmony_ci	cascade = platform_get_drvdata(pdev);
189962306a36Sopenharmony_ci	docg3_floors = cascade->floors;
190062306a36Sopenharmony_ci	mtd = docg3_floors[0];
190162306a36Sopenharmony_ci	docg3 = mtd->priv;
190262306a36Sopenharmony_ci
190362306a36Sopenharmony_ci	doc_dbg("docg3_resume()\n");
190462306a36Sopenharmony_ci	for (i = 0; i < 12; i++)
190562306a36Sopenharmony_ci		doc_readb(docg3, DOC_IOSPACE_IPL);
190662306a36Sopenharmony_ci	return 0;
190762306a36Sopenharmony_ci}
190862306a36Sopenharmony_ci
190962306a36Sopenharmony_ci/**
191062306a36Sopenharmony_ci * docg3_suspend - Put in low power mode the docg3 floor
191162306a36Sopenharmony_ci * @pdev: platform device
191262306a36Sopenharmony_ci * @state: power state
191362306a36Sopenharmony_ci *
191462306a36Sopenharmony_ci * Shuts off most of docg3 circuitery to lower power consumption.
191562306a36Sopenharmony_ci *
191662306a36Sopenharmony_ci * Returns 0 if suspend succeeded, -EIO if chip refused suspend
191762306a36Sopenharmony_ci */
191862306a36Sopenharmony_cistatic int docg3_suspend(struct platform_device *pdev, pm_message_t state)
191962306a36Sopenharmony_ci{
192062306a36Sopenharmony_ci	int floor, i;
192162306a36Sopenharmony_ci	struct docg3_cascade *cascade;
192262306a36Sopenharmony_ci	struct mtd_info **docg3_floors, *mtd;
192362306a36Sopenharmony_ci	struct docg3 *docg3;
192462306a36Sopenharmony_ci	u8 ctrl, pwr_down;
192562306a36Sopenharmony_ci
192662306a36Sopenharmony_ci	cascade = platform_get_drvdata(pdev);
192762306a36Sopenharmony_ci	docg3_floors = cascade->floors;
192862306a36Sopenharmony_ci	for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++) {
192962306a36Sopenharmony_ci		mtd = docg3_floors[floor];
193062306a36Sopenharmony_ci		if (!mtd)
193162306a36Sopenharmony_ci			continue;
193262306a36Sopenharmony_ci		docg3 = mtd->priv;
193362306a36Sopenharmony_ci
193462306a36Sopenharmony_ci		doc_writeb(docg3, floor, DOC_DEVICESELECT);
193562306a36Sopenharmony_ci		ctrl = doc_register_readb(docg3, DOC_FLASHCONTROL);
193662306a36Sopenharmony_ci		ctrl &= ~DOC_CTRL_VIOLATION & ~DOC_CTRL_CE;
193762306a36Sopenharmony_ci		doc_writeb(docg3, ctrl, DOC_FLASHCONTROL);
193862306a36Sopenharmony_ci
193962306a36Sopenharmony_ci		for (i = 0; i < 10; i++) {
194062306a36Sopenharmony_ci			usleep_range(3000, 4000);
194162306a36Sopenharmony_ci			pwr_down = doc_register_readb(docg3, DOC_POWERMODE);
194262306a36Sopenharmony_ci			if (pwr_down & DOC_POWERDOWN_READY)
194362306a36Sopenharmony_ci				break;
194462306a36Sopenharmony_ci		}
194562306a36Sopenharmony_ci		if (pwr_down & DOC_POWERDOWN_READY) {
194662306a36Sopenharmony_ci			doc_dbg("docg3_suspend(): floor %d powerdown ok\n",
194762306a36Sopenharmony_ci				floor);
194862306a36Sopenharmony_ci		} else {
194962306a36Sopenharmony_ci			doc_err("docg3_suspend(): floor %d powerdown failed\n",
195062306a36Sopenharmony_ci				floor);
195162306a36Sopenharmony_ci			return -EIO;
195262306a36Sopenharmony_ci		}
195362306a36Sopenharmony_ci	}
195462306a36Sopenharmony_ci
195562306a36Sopenharmony_ci	mtd = docg3_floors[0];
195662306a36Sopenharmony_ci	docg3 = mtd->priv;
195762306a36Sopenharmony_ci	doc_set_asic_mode(docg3, DOC_ASICMODE_POWERDOWN);
195862306a36Sopenharmony_ci	return 0;
195962306a36Sopenharmony_ci}
196062306a36Sopenharmony_ci
196162306a36Sopenharmony_ci/**
196262306a36Sopenharmony_ci * docg3_probe - Probe the IO space for a DiskOnChip G3 chip
196362306a36Sopenharmony_ci * @pdev: platform device
196462306a36Sopenharmony_ci *
196562306a36Sopenharmony_ci * Probes for a G3 chip at the specified IO space in the platform data
196662306a36Sopenharmony_ci * ressources. The floor 0 must be available.
196762306a36Sopenharmony_ci *
196862306a36Sopenharmony_ci * Returns 0 on success, -ENOMEM, -ENXIO on error
196962306a36Sopenharmony_ci */
197062306a36Sopenharmony_cistatic int __init docg3_probe(struct platform_device *pdev)
197162306a36Sopenharmony_ci{
197262306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
197362306a36Sopenharmony_ci	struct mtd_info *mtd;
197462306a36Sopenharmony_ci	struct resource *ress;
197562306a36Sopenharmony_ci	void __iomem *base;
197662306a36Sopenharmony_ci	int ret, floor;
197762306a36Sopenharmony_ci	struct docg3_cascade *cascade;
197862306a36Sopenharmony_ci
197962306a36Sopenharmony_ci	ret = -ENXIO;
198062306a36Sopenharmony_ci	ress = platform_get_resource(pdev, IORESOURCE_MEM, 0);
198162306a36Sopenharmony_ci	if (!ress) {
198262306a36Sopenharmony_ci		dev_err(dev, "No I/O memory resource defined\n");
198362306a36Sopenharmony_ci		return ret;
198462306a36Sopenharmony_ci	}
198562306a36Sopenharmony_ci
198662306a36Sopenharmony_ci	ret = -ENOMEM;
198762306a36Sopenharmony_ci	base = devm_ioremap(dev, ress->start, DOC_IOSPACE_SIZE);
198862306a36Sopenharmony_ci	if (!base) {
198962306a36Sopenharmony_ci		dev_err(dev, "devm_ioremap dev failed\n");
199062306a36Sopenharmony_ci		return ret;
199162306a36Sopenharmony_ci	}
199262306a36Sopenharmony_ci
199362306a36Sopenharmony_ci	cascade = devm_kcalloc(dev, DOC_MAX_NBFLOORS, sizeof(*cascade),
199462306a36Sopenharmony_ci			       GFP_KERNEL);
199562306a36Sopenharmony_ci	if (!cascade)
199662306a36Sopenharmony_ci		return ret;
199762306a36Sopenharmony_ci	cascade->base = base;
199862306a36Sopenharmony_ci	mutex_init(&cascade->lock);
199962306a36Sopenharmony_ci	cascade->bch = bch_init(DOC_ECC_BCH_M, DOC_ECC_BCH_T,
200062306a36Sopenharmony_ci				DOC_ECC_BCH_PRIMPOLY, false);
200162306a36Sopenharmony_ci	if (!cascade->bch)
200262306a36Sopenharmony_ci		return ret;
200362306a36Sopenharmony_ci
200462306a36Sopenharmony_ci	for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++) {
200562306a36Sopenharmony_ci		mtd = doc_probe_device(cascade, floor, dev);
200662306a36Sopenharmony_ci		if (IS_ERR(mtd)) {
200762306a36Sopenharmony_ci			ret = PTR_ERR(mtd);
200862306a36Sopenharmony_ci			goto err_probe;
200962306a36Sopenharmony_ci		}
201062306a36Sopenharmony_ci		if (!mtd) {
201162306a36Sopenharmony_ci			if (floor == 0)
201262306a36Sopenharmony_ci				goto notfound;
201362306a36Sopenharmony_ci			else
201462306a36Sopenharmony_ci				continue;
201562306a36Sopenharmony_ci		}
201662306a36Sopenharmony_ci		cascade->floors[floor] = mtd;
201762306a36Sopenharmony_ci		ret = mtd_device_parse_register(mtd, part_probes, NULL, NULL,
201862306a36Sopenharmony_ci						0);
201962306a36Sopenharmony_ci		if (ret)
202062306a36Sopenharmony_ci			goto err_probe;
202162306a36Sopenharmony_ci
202262306a36Sopenharmony_ci		doc_dbg_register(cascade->floors[floor]);
202362306a36Sopenharmony_ci	}
202462306a36Sopenharmony_ci
202562306a36Sopenharmony_ci	ret = doc_register_sysfs(pdev, cascade);
202662306a36Sopenharmony_ci	if (ret)
202762306a36Sopenharmony_ci		goto err_probe;
202862306a36Sopenharmony_ci
202962306a36Sopenharmony_ci	platform_set_drvdata(pdev, cascade);
203062306a36Sopenharmony_ci	return 0;
203162306a36Sopenharmony_ci
203262306a36Sopenharmony_cinotfound:
203362306a36Sopenharmony_ci	ret = -ENODEV;
203462306a36Sopenharmony_ci	dev_info(dev, "No supported DiskOnChip found\n");
203562306a36Sopenharmony_cierr_probe:
203662306a36Sopenharmony_ci	bch_free(cascade->bch);
203762306a36Sopenharmony_ci	for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++)
203862306a36Sopenharmony_ci		if (cascade->floors[floor])
203962306a36Sopenharmony_ci			doc_release_device(cascade->floors[floor]);
204062306a36Sopenharmony_ci	return ret;
204162306a36Sopenharmony_ci}
204262306a36Sopenharmony_ci
204362306a36Sopenharmony_ci/**
204462306a36Sopenharmony_ci * docg3_release - Release the driver
204562306a36Sopenharmony_ci * @pdev: the platform device
204662306a36Sopenharmony_ci *
204762306a36Sopenharmony_ci * Returns 0
204862306a36Sopenharmony_ci */
204962306a36Sopenharmony_cistatic int docg3_release(struct platform_device *pdev)
205062306a36Sopenharmony_ci{
205162306a36Sopenharmony_ci	struct docg3_cascade *cascade = platform_get_drvdata(pdev);
205262306a36Sopenharmony_ci	struct docg3 *docg3 = cascade->floors[0]->priv;
205362306a36Sopenharmony_ci	int floor;
205462306a36Sopenharmony_ci
205562306a36Sopenharmony_ci	doc_unregister_sysfs(pdev, cascade);
205662306a36Sopenharmony_ci	for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++)
205762306a36Sopenharmony_ci		if (cascade->floors[floor])
205862306a36Sopenharmony_ci			doc_release_device(cascade->floors[floor]);
205962306a36Sopenharmony_ci
206062306a36Sopenharmony_ci	bch_free(docg3->cascade->bch);
206162306a36Sopenharmony_ci	return 0;
206262306a36Sopenharmony_ci}
206362306a36Sopenharmony_ci
206462306a36Sopenharmony_ci#ifdef CONFIG_OF
206562306a36Sopenharmony_cistatic const struct of_device_id docg3_dt_ids[] = {
206662306a36Sopenharmony_ci	{ .compatible = "m-systems,diskonchip-g3" },
206762306a36Sopenharmony_ci	{}
206862306a36Sopenharmony_ci};
206962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, docg3_dt_ids);
207062306a36Sopenharmony_ci#endif
207162306a36Sopenharmony_ci
207262306a36Sopenharmony_cistatic struct platform_driver g3_driver = {
207362306a36Sopenharmony_ci	.driver		= {
207462306a36Sopenharmony_ci		.name	= "docg3",
207562306a36Sopenharmony_ci		.of_match_table = of_match_ptr(docg3_dt_ids),
207662306a36Sopenharmony_ci	},
207762306a36Sopenharmony_ci	.suspend	= docg3_suspend,
207862306a36Sopenharmony_ci	.resume		= docg3_resume,
207962306a36Sopenharmony_ci	.remove		= docg3_release,
208062306a36Sopenharmony_ci};
208162306a36Sopenharmony_ci
208262306a36Sopenharmony_cimodule_platform_driver_probe(g3_driver, docg3_probe);
208362306a36Sopenharmony_ci
208462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
208562306a36Sopenharmony_ciMODULE_AUTHOR("Robert Jarzmik <robert.jarzmik@free.fr>");
208662306a36Sopenharmony_ciMODULE_DESCRIPTION("MTD driver for DiskOnChip G3");
2087