• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-R7000-V1.0.7.12_1.2.5/components/opensource/linux/linux-2.6.36/drivers/mtd/bcm947xx/nand/
1/*
2 * Broadcom SiliconBackplane chipcommon serial flash interface
3 *
4 * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved.
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 *
18 * $Id $
19 */
20
21#include <linux/version.h>
22
23#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36)
24#include <linux/config.h>
25#endif
26
27#include <linux/reciprocal_div.h>
28#include <linux/module.h>
29#include <linux/slab.h>
30#include <linux/ioport.h>
31#include <linux/mtd/mtd.h>
32#include <linux/mtd/nand.h>
33#include <linux/mtd/partitions.h>
34
35#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36)
36#include <linux/mtd/compatmac.h>
37#else
38/* #include <linux/mtd/nand.h> */
39#endif
40
41#include <linux/errno.h>
42#include <linux/pci.h>
43#include <linux/delay.h>
44#include <asm/io.h>
45
46#include <typedefs.h>
47#include <osl.h>
48#include <bcmutils.h>
49#include <bcmdevs.h>
50#include <bcmnvram.h>
51#include <siutils.h>
52#include <hndpci.h>
53#include <pcicfg.h>
54#include <hndsoc.h>
55#include <sbchipc.h>
56#include <hndnand.h>
57
58#ifdef CONFIG_MTD_PARTITIONS
59extern struct mtd_partition *
60init_nflash_mtd_partitions(hndnand_t *nfl, struct mtd_info *mtd, size_t size);
61
62struct mtd_partition *nflash_parts;
63#endif
64
65/* Mutexing is version-dependent */
66extern struct nand_hw_control *nand_hwcontrol_lock_init(void);
67
68struct nflash_mtd {
69	si_t *sih;
70	hndnand_t *nfl;
71	struct mtd_info mtd;
72	struct nand_hw_control *controller;
73	struct mtd_erase_region_info region;
74	unsigned char *map;
75};
76
77/* Private global state */
78static struct nflash_mtd nflash;
79
80static int _nflash_get_device(struct nflash_mtd *nflash);
81static void _nflash_release_device(struct nflash_mtd *nflash);
82
83#define	NFLASH_LOCK(nflash)	_nflash_get_device(nflash)
84#define	NFLASH_UNLOCK(nflash)	_nflash_release_device(nflash)
85
86static int
87_nflash_get_device(struct nflash_mtd *nflash)
88{
89	spinlock_t *lock = &nflash->controller->lock;
90	wait_queue_head_t *wq = &nflash->controller->wq;
91	struct nand_chip *chip;
92	DECLARE_WAITQUEUE(wait, current);
93
94retry:
95	spin_lock(lock);
96
97	chip = nflash->controller->active;
98	if (!chip || chip->state == FL_READY)
99		return 0;
100
101	set_current_state(TASK_UNINTERRUPTIBLE);
102	add_wait_queue(wq, &wait);
103	spin_unlock(lock);
104	schedule();
105	remove_wait_queue(wq, &wait);
106	goto retry;
107}
108
109static void
110_nflash_release_device(struct nflash_mtd *nflash)
111{
112	wake_up(&nflash->controller->wq);
113	spin_unlock(&nflash->controller->lock);
114}
115
116static int
117_nflash_mtd_read(struct mtd_info *mtd, struct mtd_partition *part,
118	loff_t from, size_t len, size_t *retlen, u_char *buf)
119{
120	struct nflash_mtd *nflash = (struct nflash_mtd *) mtd->priv;
121	int bytes, ret = 0;
122	uint extra = 0;
123	uchar *tmpbuf = NULL;
124	int size;
125	uint offset, blocksize, mask, blk_offset, off;
126	uint skip_bytes = 0, good_bytes = 0, page_size;
127	int blk_idx, i;
128	int need_copy = 0;
129	uchar *ptr = NULL;
130
131	/* Locate the part */
132	if (!part) {
133		for (i = 0; nflash_parts[i].name; i++) {
134			if (from >= nflash_parts[i].offset &&
135			((nflash_parts[i+1].name == NULL) || (from < nflash_parts[i+1].offset))) {
136				part = &nflash_parts[i];
137				break;
138			}
139		}
140		if (!part)
141			return -EINVAL;
142	}
143	/* Check address range */
144	if (!len)
145		return 0;
146	if ((from + len) > mtd->size)
147		return -EINVAL;
148	offset = from;
149	page_size = nflash->nfl->pagesize;
150	if ((offset & (page_size - 1)) != 0) {
151		extra = offset & (page_size - 1);
152		offset -= extra;
153		len += extra;
154		need_copy = 1;
155	}
156	size = (len + (page_size - 1)) & ~(page_size - 1);
157	if (size != len)
158		need_copy = 1;
159	if (!need_copy) {
160		ptr = buf;
161	} else {
162		NFLASH_UNLOCK(nflash);
163		tmpbuf = (uchar *)kmalloc(size, GFP_KERNEL);
164		NFLASH_LOCK(nflash);
165		ptr = tmpbuf;
166	}
167
168	blocksize = mtd->erasesize;
169	mask = blocksize - 1;
170	blk_offset = offset & ~mask;
171	good_bytes = part->offset & ~mask;
172	/* Check and skip bad blocks */
173	for (blk_idx = good_bytes/blocksize; blk_idx < mtd->eraseregions->numblocks; blk_idx++) {
174		if (nflash->map[blk_idx] != 0) {
175			skip_bytes += blocksize;
176		} else {
177			if (good_bytes == blk_offset)
178				break;
179			good_bytes += blocksize;
180		}
181	}
182	if (blk_idx == mtd->eraseregions->numblocks) {
183		ret = -EINVAL;
184		goto done;
185	}
186	blk_offset = blocksize * blk_idx;
187	*retlen = 0;
188	while (len > 0) {
189		off = offset + skip_bytes;
190
191		/* Check and skip bad blocks */
192		if (off >= (blk_offset + blocksize)) {
193			blk_offset += blocksize;
194			blk_idx++;
195			while ((nflash->map[blk_idx] != 0) &&
196			       (blk_offset < mtd->size)) {
197				skip_bytes += blocksize;
198				blk_offset += blocksize;
199				blk_idx++;
200			}
201			if (blk_offset >= mtd->size) {
202				ret = -EINVAL;
203				goto done;
204			}
205			off = offset + skip_bytes;
206		}
207
208		if ((bytes = hndnand_read(nflash->nfl,
209			off, page_size, ptr)) < 0) {
210			ret = bytes;
211			goto done;
212		}
213		if (bytes > len)
214			bytes = len;
215		offset += bytes;
216		len -= bytes;
217		ptr += bytes;
218		*retlen += bytes;
219	}
220
221done:
222	if (tmpbuf) {
223		*retlen -= extra;
224		memcpy(buf, tmpbuf+extra, *retlen);
225		kfree(tmpbuf);
226	}
227	return ret;
228}
229
230static int
231nflash_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
232{
233	int ret;
234	struct nflash_mtd *nflash = (struct nflash_mtd *) mtd->priv;
235
236	NFLASH_LOCK(nflash);
237	ret = _nflash_mtd_read(mtd, NULL, from, len, retlen, buf);
238	NFLASH_UNLOCK(nflash);
239
240	return ret;
241}
242
243static int
244nflash_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf)
245{
246	struct nflash_mtd *nflash = (struct nflash_mtd *) mtd->priv;
247	int bytes, ret = 0;
248	struct mtd_partition *part = NULL;
249	u_char *block = NULL;
250	u_char *ptr = (u_char *)buf;
251	uint offset, blocksize, mask, blk_offset, off;
252	uint skip_bytes = 0, good_bytes = 0;
253	int blk_idx, i;
254	int read_len, write_len, copy_len = 0;
255	loff_t from = to;
256	u_char *write_ptr;
257	int docopy = 1;
258	uint r_blocksize, part_blk_start, part_blk_end;
259
260	/* Locate the part */
261	for (i = 0; nflash_parts[i].name; i++) {
262		if (to >= nflash_parts[i].offset &&
263			((nflash_parts[i+1].name == NULL) ||
264			(to < (nflash_parts[i].offset + nflash_parts[i].size)))) {
265			part = &nflash_parts[i];
266			break;
267		}
268	}
269	if (!part)
270		return -EINVAL;
271	/* Check address range */
272	if (!len)
273		return 0;
274	if ((to + len) > (part->offset + part->size))
275		return -EINVAL;
276	offset = to;
277	blocksize = mtd->erasesize;
278	r_blocksize = reciprocal_value(blocksize);
279
280	if (!(block = kmalloc(blocksize, GFP_KERNEL)))
281		return -ENOMEM;
282
283	NFLASH_LOCK(nflash);
284
285	mask = blocksize - 1;
286	/* Check and skip bad blocks */
287	blk_offset = offset & ~mask;
288	good_bytes = part->offset & ~mask;
289	part_blk_start = reciprocal_divide(good_bytes, r_blocksize);
290	part_blk_end = reciprocal_divide(part->offset + part->size, r_blocksize);
291
292	for (blk_idx = part_blk_start;  blk_idx < part_blk_end; blk_idx++) {
293		if (nflash->map[blk_idx] != 0) {
294			skip_bytes += blocksize;
295		} else {
296			if (good_bytes == blk_offset)
297				break;
298			good_bytes += blocksize;
299		}
300	}
301	if (blk_idx == part_blk_end) {
302		ret = -EINVAL;
303		goto done;
304	}
305	blk_offset = blocksize * blk_idx;
306	/* Backup and erase one block at a time */
307	*retlen = 0;
308	while (len) {
309		if (docopy) {
310			/* Align offset */
311			from = offset & ~mask;
312			/* Copy existing data into holding block if necessary */
313			if (((offset & (blocksize-1)) != 0) || (len < blocksize)) {
314				ret = _nflash_mtd_read(mtd, part, from, blocksize,
315					&read_len, block);
316				if (ret)
317					goto done;
318				if (read_len != blocksize) {
319					ret = -EINVAL;
320					goto done;
321				}
322			}
323			/* Copy input data into holding block */
324			copy_len = min(len, blocksize - (offset & mask));
325			memcpy(block + (offset & mask), ptr, copy_len);
326		}
327		off = (uint) from + skip_bytes;
328		/* Erase block */
329		if ((ret = hndnand_erase(nflash->nfl, off)) < 0) {
330				hndnand_mark_badb(nflash->nfl, off);
331				nflash->map[blk_idx] = 1;
332				skip_bytes += blocksize;
333				docopy = 0;
334		}
335		else {
336			/* Write holding block */
337			write_ptr = block;
338			write_len = blocksize;
339			while (write_len) {
340				if ((bytes = hndnand_write(nflash->nfl,
341				from + skip_bytes, (uint) write_len,
342				(uchar *) write_ptr)) < 0) {
343					hndnand_mark_badb(nflash->nfl, off);
344					nflash->map[blk_idx] = 1;
345					skip_bytes += blocksize;
346					docopy = 0;
347					break;
348				}
349				from += bytes;
350				write_len -= bytes;
351				write_ptr += bytes;
352				docopy = 1;
353			}
354			if (docopy) {
355				offset += copy_len;
356				len -= copy_len;
357				ptr += copy_len;
358				*retlen += copy_len;
359			}
360		}
361		/* Check and skip bad blocks */
362		if (len) {
363			blk_offset += blocksize;
364			blk_idx++;
365			while ((nflash->map[blk_idx] != 0) &&
366			       (blk_offset < (part->offset+part->size))) {
367				skip_bytes += blocksize;
368				blk_offset += blocksize;
369				blk_idx++;
370			}
371			if (blk_offset >= (part->offset+part->size)) {
372				ret = -EINVAL;
373				goto done;
374			}
375		}
376	}
377done:
378	NFLASH_UNLOCK(nflash);
379
380	if (block)
381		kfree(block);
382	return ret;
383}
384
385static int
386nflash_mtd_erase(struct mtd_info *mtd, struct erase_info *erase)
387{
388	struct nflash_mtd *nflash = (struct nflash_mtd *) mtd->priv;
389	struct mtd_partition *part = NULL;
390	int i, ret = 0;
391	uint addr, len, blocksize;
392	uint part_start_blk, part_end_blk;
393	uint blknum, new_addr, erase_blknum;
394	uint reciprocal_blocksize;
395
396	addr = erase->addr;
397	len = erase->len;
398
399	blocksize = mtd->erasesize;
400	reciprocal_blocksize = reciprocal_value(blocksize);
401
402	/* Check address range */
403	if (!len)
404		return 0;
405
406	if ((addr + len) > mtd->size)
407		return -EINVAL;
408
409	if (addr & (blocksize - 1))
410		return -EINVAL;
411
412	/* Locate the part */
413	for (i = 0; nflash_parts[i].name; i++) {
414		if (addr >= nflash_parts[i].offset &&
415			((addr + len) <= (nflash_parts[i].offset + nflash_parts[i].size))) {
416			part = &nflash_parts[i];
417			break;
418		}
419	}
420
421	if (!part)
422		return -EINVAL;
423
424	NFLASH_LOCK(nflash);
425
426	/* Find the effective start block address to erase */
427	part_start_blk = reciprocal_divide(part->offset & ~(blocksize-1),
428		reciprocal_blocksize);
429	part_end_blk = reciprocal_divide(((part->offset + part->size) + (blocksize-1)),
430		reciprocal_blocksize);
431
432	new_addr = part_start_blk * blocksize;
433	/* The block number to be skipped relative to the start address of
434	 * the MTD partition
435	 */
436	blknum = reciprocal_divide(addr - new_addr, reciprocal_blocksize);
437
438	for (i = part_start_blk; (i < part_end_blk) && (blknum > 0); i++) {
439		if (nflash->map[i] != 0) {
440			new_addr += blocksize;
441		} else {
442			new_addr += blocksize;
443			blknum--;
444		}
445	}
446
447	/* Erase the blocks from the new block address */
448	erase_blknum = reciprocal_divide(len + (blocksize-1), reciprocal_blocksize);
449
450	if ((new_addr + (erase_blknum * blocksize)) > (part->offset + part->size)) {
451		ret = -EINVAL;
452		goto done;
453	}
454
455	for (i = new_addr; erase_blknum; i += blocksize) {
456		/* Skip bad block erase */
457		uint j = reciprocal_divide(i, reciprocal_blocksize);
458		if (nflash->map[j] != 0) {
459			continue;
460		}
461
462		if ((ret = hndnand_erase(nflash->nfl, i)) < 0) {
463			hndnand_mark_badb(nflash->nfl, i);
464			nflash->map[i / blocksize] = 1;
465		} else {
466			erase_blknum--;
467		}
468	}
469
470done:
471	/* Set erase status */
472	if (ret)
473		erase->state = MTD_ERASE_FAILED;
474	else
475		erase->state = MTD_ERASE_DONE;
476
477	NFLASH_UNLOCK(nflash);
478
479	/* Call erase callback */
480	if (erase->callback)
481		erase->callback(erase);
482
483	return ret;
484}
485
486#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE)
487#define nflash_mtd_init init_module
488#define nflash_mtd_exit cleanup_module
489#endif
490
491static int __init
492nflash_mtd_init(void)
493{
494	int ret = 0;
495	hndnand_t *info;
496#ifdef CONFIG_MTD_PARTITIONS
497	struct mtd_partition *parts;
498	int i;
499#endif
500
501	memset(&nflash, 0, sizeof(struct nflash_mtd));
502
503	/* attach to the backplane */
504	if (!(nflash.sih = si_kattach(SI_OSH))) {
505		printk(KERN_ERR "nflash: error attaching to backplane\n");
506		ret = -EIO;
507		goto fail;
508	}
509
510	/* Initialize serial flash access */
511	if (!(info = hndnand_init(nflash.sih))) {
512		printk(KERN_ERR "nflash: found no supported devices\n");
513		ret = -ENODEV;
514		goto fail;
515	}
516	nflash.nfl = info;
517
518	/* Setup region info */
519	nflash.region.offset = 0;
520	nflash.region.erasesize = info->blocksize;
521	nflash.region.numblocks = info->numblocks;
522	if (nflash.region.erasesize > nflash.mtd.erasesize)
523		nflash.mtd.erasesize = nflash.region.erasesize;
524	/* At most 2GB is supported */
525	nflash.mtd.size = (info->size >= (1 << 11)) ? (1 << 31) : (info->size << 20);
526	nflash.mtd.numeraseregions = 1;
527	nflash.map = (unsigned char *)kmalloc(info->numblocks, GFP_KERNEL);
528	if (nflash.map)
529		memset(nflash.map, 0, info->numblocks);
530
531	/* Register with MTD */
532	nflash.mtd.name = "nflash";
533	nflash.mtd.type = MTD_NANDFLASH;
534	nflash.mtd.flags = MTD_CAP_NANDFLASH;
535	nflash.mtd.eraseregions = &nflash.region;
536	nflash.mtd.erase = nflash_mtd_erase;
537	nflash.mtd.read = nflash_mtd_read;
538	nflash.mtd.write = nflash_mtd_write;
539	nflash.mtd.writesize = info->pagesize;
540	nflash.mtd.priv = &nflash;
541	nflash.mtd.owner = THIS_MODULE;
542	nflash.controller = nand_hwcontrol_lock_init();
543	if (!nflash.controller)
544		return -ENOMEM;
545
546	/* Scan bad block */
547	NFLASH_LOCK(&nflash);
548	for (i = 0; i < info->numblocks; i++) {
549		if (hndnand_checkbadb(nflash.nfl, (i * info->blocksize)) != 0) {
550			nflash.map[i] = 1;
551		}
552	}
553	NFLASH_UNLOCK(&nflash);
554
555#ifdef CONFIG_MTD_PARTITIONS
556	parts = init_nflash_mtd_partitions(info, &nflash.mtd, nflash.mtd.size);
557	if (!parts)
558		goto fail;
559
560	for (i = 0; parts[i].name; i++)
561		;
562
563	ret = add_mtd_partitions(&nflash.mtd, parts, i);
564	if (ret) {
565		printk(KERN_ERR "nflash: add_mtd failed\n");
566		goto fail;
567	}
568	nflash_parts = parts;
569#endif
570	return 0;
571
572fail:
573	return ret;
574}
575
576static void __exit
577nflash_mtd_exit(void)
578{
579#ifdef CONFIG_MTD_PARTITIONS
580	del_mtd_partitions(&nflash.mtd);
581#else
582	del_mtd_device(&nflash.mtd);
583#endif
584}
585
586module_init(nflash_mtd_init);
587module_exit(nflash_mtd_exit);
588