aboutsummaryrefslogtreecommitdiff
path: root/drivers/mtd/nand/gpmi1/gpmi-bbt.c
blob: 7888e5f2703d7dc9994bf83cd8002952d062761d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
/*
 * Freescale i.MX28 GPMI (General-Purpose-Media-Interface)
 *
 * Author: dmitry pervushin <dimka@embeddedalley.com>
 *
 * Copyright 2008-2010 Freescale Semiconductor, Inc.
 * Copyright 2008 Embedded Alley Solutions, Inc.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/partitions.h>
#include <linux/dma-mapping.h>
#include <linux/ctype.h>
#include <mach/dma.h>
#include "gpmi.h"

/* Fingerprints for Boot Control Blocks. */

#define SIG_FCB		"FCB "
#define SIG_DBBT	"DBBT"
#define SIG_SIZE	4

/*
 * The equivalent of the BOOT_SEARCH_COUNT field in the OTP bits. That is, the
 * logarithm to base 2 of the number of strides in a search area (a stride is
 * 64 pages).
 */

static int boot_search_count;

module_param(boot_search_count, int, 0400);

/*
 * The size, in pages, of a search area stride.
 *
 * This number is dictated by the ROM, so it's not clear why it isn't at least
 * const, or perhaps a macro.
 */

static const int stride_size_in_pages = 64;

/*
 * read_page -
 *
 * @mtd:    The owning MTD.
 * @start:  The offset at which to begin reading.
 * @data:   A pointer to a buff that will receive the data. This pointer may be
 *          NULL, in which case this function will allocate a buffer.
 * @raw:    If true, indicates that the caller doesn't want to use ECC.
 */
static void *read_page(struct mtd_info *mtd, loff_t start, void *data, int raw)
{
	int ret;
	struct mtd_oob_ops ops;

	/* If the caller didn't send in his own buffer, allocate one. */

	if (!data)
		data = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL);
	if (!data)
		return NULL;

	/* Check if the caller wants to use ECC. */

	if (raw)
		ops.mode = MTD_OOB_RAW;
	else
		ops.mode = MTD_OOB_PLACE;

	/*
	 * Call nand_do_read_ops() to do the dirty work.
	 */

	ops.datbuf = data;
	ops.len = mtd->writesize;
	ops.oobbuf = data + mtd->writesize;
	ops.ooblen = mtd->oobsize;
	ops.ooboffs = 0;
	ret = nand_do_read_ops(mtd, start, &ops);

	if (ret)
		return NULL;
	return data;
}

/*
 * gpmi_write_fcbs - Writes FCBs to the medium.
 *
 * @mtd:  The owning MTD.
 */
static void gpmi_write_fcbs(struct mtd_info *mtd)
{
	unsigned int  i;
	unsigned int  page_size_in_bytes;
	unsigned int  block_size_in_bytes;
	unsigned int  block_size_in_pages;
	unsigned int  search_area_size_in_strides;
	unsigned int  search_area_size_in_pages;
	unsigned int  search_area_size_in_blocks;
	void  *fcb;
	struct nand_chip *chip = mtd->priv;
	struct mtd_oob_ops ops;
	struct erase_info  instr;

	/* Compute some important facts. */

	page_size_in_bytes  = mtd->writesize;
	block_size_in_bytes = 1 << chip->phys_erase_shift;
	block_size_in_pages = 1 << (chip->phys_erase_shift - chip->page_shift);

	search_area_size_in_strides = (1 << boot_search_count) - 1;
	search_area_size_in_pages   =
			search_area_size_in_strides * stride_size_in_pages + 1;
	search_area_size_in_blocks  =
		(search_area_size_in_pages + (block_size_in_pages - 1)) /
							block_size_in_pages;

	/* Allocate an I/O buffer for the FCB page, with OOB. */

	fcb = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL);
	if (!fcb)
		return;

	/* Write the FCB signature into the buffer. */

	memcpy(((uint8_t *) fcb) + 0x10, SIG_FCB, SIG_SIZE);

	/* Erase the entire search area. */

	for (i = 0; i < search_area_size_in_blocks; i++) {
		memset(&instr, 0, sizeof(instr));
		instr.mtd = mtd;
		instr.addr = i * block_size_in_bytes;
		instr.len = block_size_in_bytes;
		nand_erase_nand(mtd, &instr, 0);
	}

	/* Construct the data structure for the write operation. */

	ops.datbuf = (u8 *)fcb;
	ops.len = mtd->writesize;
	ops.oobbuf = (u8 *)fcb + mtd->writesize;
	ops.ooblen = mtd->oobsize;
	ops.ooboffs = 0;
	ops.mode = MTD_OOB_RAW;

	/* Loop over FCB locations in the search area. */

	for (i = 0; i <= search_area_size_in_strides; i++) {

		printk(KERN_NOTICE"Writing FCB in page 0x%08x\n",
				i * stride_size_in_pages);

		nand_do_write_ops(mtd,
			i * stride_size_in_pages * page_size_in_bytes, &ops);

	}

	/* Free our buffer. */

	kfree(fcb);

}

/*
 * gpmi_scan_for_fcb - Scans the medium for an FCB.
 *
 * @mtd:  The owning MTD.
 */
static int gpmi_scan_for_fcb(struct mtd_info *mtd)
{
	int result = 0;
	int page;
	u8 *pg;

	/* If the boot search count is 0, make it 2. */

	if (boot_search_count == 0)
		boot_search_count = 2;

	/* Loop through the medium, searching for the FCB. */

	printk(KERN_NOTICE"Scanning for FCB...\n");

	pg = NULL;

	for (page = 0;
		page < ((1 << boot_search_count) * stride_size_in_pages);
						page += stride_size_in_pages) {

		/* Read the current page. */

		pg = read_page(mtd, page * mtd->writesize, pg, !0);

		printk(KERN_NOTICE"Looking for FCB in page 0x%08x\n", page);

		/*
		 * A valid FCB page contains the following:
		 *
		 *     +------------+
		 *           .
		 *           .
		 *       Don't Care
		 *           .
		 *           .
		 *     +------------+ 1036
		 *     |            |
		 *     |  FCB ECC   |
		 *     |            |
		 *     +------------+  524
		 *     |            |
		 *     |    FCB     |
		 *     |            |
		 *     +------------+   12
		 *     | Don't Care |
		 *     +------------+    0
		 *
		 * Within the FCB, there is a "fingerprint":
		 *
		 *     +-----------+--------------------+
		 *     | Offset In |                    |
		 *     | FCB Page  |    Fingerprint     |
		 *     +-----------+--------------------+
		 *     |   0x10    | "FCB "  0x46434220 |
		 *     +-----------+--------------------+
		 */

		/* Check for the fingerprint. */

		if (memcmp(pg + 16, SIG_FCB, SIG_SIZE) != 0)
			continue;

		printk(KERN_NOTICE"Found FCB in page 0x%08X\n", page);

		result = !0;

		break;

	}

	if (!result)
		printk(KERN_NOTICE"No FCB found\n");

	/* Free the page buffer */

	kfree(pg);

	return result;

}

/**
 * gpmi_block_bad - Claims all blocks are good.
 *
 * @mtd:      The owning MTD.
 * @ofs:      The offset of the block.
 * @getchip:  ??
 *
 * In principle, this function is called when the NAND Flash MTD system isn't
 * allowed to keep an in-memory bad block table, so it must ask the driver
 * for bad block information.
 *
 * In fact, we permit the NAND Flash MTD system to have an in-memory BBT, so
 * this function is *only* called when we take it away.
 *
 * We take away the in-memory BBT when the user sets the "ignorebad" parameter,
 * which indicates that all blocks should be reported good.
 *
 * Thus, this function is only called when we want *all* blocks to look good,
 * so it need do nothing more than always return success.
 */
int gpmi_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
{
	return 0;
}

/**
 * transcribe_block_mark - Transcribes a block mark.
 *
 * @mtd:  The owning MTD.
 * @ofs:  Identifies the block of interest.
 */
static void transcribe_block_mark(struct mtd_info *mtd, loff_t ofs)

{
	int page;
	struct nand_chip *chip = mtd->priv;
	int chipnr;

	/*
	 * Compute the position of the block mark within the OOB (this code
	 * appears to be wrong).
	 */

	int badblockpos = chip->ecc.steps * chip->ecc.bytes;

	/*
	 * Compute the page address of the first page in the block that contains
	 * the given offset.
	 */

	page = (int)(ofs >> chip->page_shift) & chip->pagemask;

	/*
	 * Compute the chip number that contains the given offset, and select
	 * it.
	 */

	chipnr = (int)(ofs >> chip->chip_shift);
	chip->select_chip(mtd, chipnr);

	/* bad block marks still are on first byte of OOB */

	badblockpos = 0;

	/* Read the block mark. */

	chip->cmdfunc(mtd, NAND_CMD_READOOB, badblockpos, page);

	if (chip->read_byte(mtd) != 0xff) {
		printk(KERN_NOTICE"Transcribing block mark in block 0x%08x\n",
				(unsigned) (ofs >> chip->phys_erase_shift));
		chip->block_markbad(mtd, ofs);
	}

	/*
	 * Deselect the chip.
	 */

	chip->select_chip(mtd, -1);

}

/**
 * gpmi_scan_bbt - Sets up to manage bad blocks.
 *
 * @mtd:  The owning MTD.
 */
int gpmi_scan_bbt(struct mtd_info *mtd)
{
	struct nand_chip *this = mtd->priv;
	int r;
	int transcription_is_needed;
	unsigned int  search_area_size_in_strides;
	unsigned  page;
	unsigned  block;
	int numblocks, from, i;
	struct nand_chip *chip = mtd->priv;

	/* Search for the FCB. */

	transcription_is_needed = !gpmi_scan_for_fcb(mtd);

	/* Check if we found the FCB. */

	if (transcription_is_needed) {

		printk(KERN_NOTICE"Transcribing bad block marks...\n");

		/*
		 * If control arrives here, the medium has no FCB, so we
		 * presume it is in common format. This means we must transcribe
		 * the block marks.
		 *
		 * Compute the number of blocks in the entire medium.
		 */

		numblocks = this->chipsize >> this->bbt_erase_shift;

		/*
		 * Loop over all the blocks in the medium, transcribing block
		 * marks as we go.
		 */

		from = 0;
		for (i = 0; i < numblocks; i++) {
			/* Transcribe the mark in this block, if needed. */
			transcribe_block_mark(mtd, from);
			from += (1 << this->bbt_erase_shift);
		}

		/*
		 * Put an FCB in the medium to indicate the block marks have
		 * been transcribed.
		 */

		gpmi_write_fcbs(mtd);

	}

	/* Use the reference implementation's BBT scan. */

	r = nand_default_bbt(mtd);

	/* Mark all NCB blocks as good. */

	search_area_size_in_strides = (1 << boot_search_count) - 1;

	for (i = 0; i <= search_area_size_in_strides; i++) {

		/* Compute the current FCB page.*/

		page = i * stride_size_in_pages;

		/* Compute the block that contains the current FCB page.*/

		block = page >> (chip->phys_erase_shift - chip->page_shift);

		/* Mark the block good. */

		printk(KERN_NOTICE"Forcing good FCB block 0x%08x\n", block);

		gpmi_block_mark_as(this, block, 0x00);

	}

	return r;
}