2 * Broadcom SiliconBackplane chipcommon serial flash interface
4 * Copyright (C) 2012, Broadcom Corporation. All Rights Reserved.
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.
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.
21 #include <linux/version.h>
23 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36)
24 #include <linux/config.h>
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/partitions.h>
34 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36)
35 #include <linux/mtd/compatmac.h>
37 /* #include <linux/mtd/nand.h> */
40 #include <linux/errno.h>
41 #include <linux/pci.h>
42 #include <linux/delay.h>
57 #ifdef CONFIG_MTD_PARTITIONS
58 extern struct mtd_partition
*
59 init_nflash_mtd_partitions(hndnand_t
*nfl
, struct mtd_info
*mtd
, size_t size
);
61 struct mtd_partition
*nflash_parts
;
64 /* Mutexing is version-dependent */
65 extern void *partitions_lock_init(void);
66 #define NFLASH_LOCK(lock) if (lock) spin_lock(lock)
67 #define NFLASH_UNLOCK(lock) if (lock) spin_unlock(lock)
73 struct mtd_erase_region_info region
;
77 /* Private global state */
78 static struct nflash_mtd nflash
;
81 _nflash_mtd_read(struct mtd_info
*mtd
, struct mtd_partition
*part
,
82 loff_t from
, size_t len
, size_t *retlen
, u_char
*buf
)
84 struct nflash_mtd
*nflash
= (struct nflash_mtd
*) mtd
->priv
;
89 uint offset
, blocksize
, mask
, blk_offset
, off
;
90 uint skip_bytes
= 0, good_bytes
= 0;
97 for (i
= 0; nflash_parts
[i
].name
; i
++) {
98 if (from
>= nflash_parts
[i
].offset
&&
99 ((nflash_parts
[i
+1].name
== NULL
) || (from
< nflash_parts
[i
+1].offset
))) {
100 part
= &nflash_parts
[i
];
107 /* Check address range */
110 if ((from
+ len
) > mtd
->size
)
113 if ((offset
& (NFL_SECTOR_SIZE
- 1)) != 0) {
114 extra
= offset
& (NFL_SECTOR_SIZE
- 1);
119 size
= (len
+ (NFL_SECTOR_SIZE
- 1)) & ~(NFL_SECTOR_SIZE
- 1);
125 tmpbuf
= (uchar
*)kmalloc(size
, GFP_KERNEL
);
129 blocksize
= mtd
->erasesize
;
130 mask
= blocksize
- 1;
131 blk_offset
= offset
& ~mask
;
132 good_bytes
= part
->offset
& ~mask
;
133 /* Check and skip bad blocks */
134 for (blk_idx
= good_bytes
/blocksize
; blk_idx
< mtd
->eraseregions
->numblocks
; blk_idx
++) {
135 if (nflash
->map
[blk_idx
] != 0) {
136 skip_bytes
+= blocksize
;
138 if (good_bytes
== blk_offset
)
140 good_bytes
+= blocksize
;
143 if (blk_idx
== mtd
->eraseregions
->numblocks
) {
147 blk_offset
= blocksize
* blk_idx
;
150 off
= offset
+ skip_bytes
;
152 /* Check and skip bad blocks */
153 if (off
>= (blk_offset
+ blocksize
)) {
154 blk_offset
+= blocksize
;
156 while ((nflash
->map
[blk_idx
] != 0) &&
157 (blk_offset
< mtd
->size
)) {
158 skip_bytes
+= blocksize
;
159 blk_offset
+= blocksize
;
162 if (blk_offset
>= mtd
->size
) {
166 off
= offset
+ skip_bytes
;
169 if ((bytes
= hndnand_read(nflash
->nfl
,
170 off
, NFL_SECTOR_SIZE
, ptr
)) < 0) {
185 memcpy(buf
, tmpbuf
+extra
, *retlen
);
192 nflash_mtd_read(struct mtd_info
*mtd
, loff_t from
, size_t len
, size_t *retlen
, u_char
*buf
)
196 NFLASH_LOCK(mtd
->mlock
);
197 ret
= _nflash_mtd_read(mtd
, NULL
, from
, len
, retlen
, buf
);
198 NFLASH_UNLOCK(mtd
->mlock
);
204 nflash_mtd_write(struct mtd_info
*mtd
, loff_t to
, size_t len
, size_t *retlen
, const u_char
*buf
)
206 struct nflash_mtd
*nflash
= (struct nflash_mtd
*) mtd
->priv
;
208 struct mtd_partition
*part
= NULL
;
209 u_char
*block
= NULL
;
210 u_char
*ptr
= (u_char
*)buf
;
211 uint offset
, blocksize
, mask
, blk_offset
, off
;
212 uint skip_bytes
= 0, good_bytes
= 0;
214 int read_len
, write_len
, copy_len
= 0;
218 uint r_blocksize
, part_blk_start
, part_blk_end
;
220 /* Locate the part */
221 for (i
= 0; nflash_parts
[i
].name
; i
++) {
222 if (to
>= nflash_parts
[i
].offset
&&
223 ((nflash_parts
[i
+1].name
== NULL
) ||
224 (to
< (nflash_parts
[i
].offset
+ nflash_parts
[i
].size
)))) {
225 part
= &nflash_parts
[i
];
231 /* Check address range */
234 if ((to
+ len
) > (part
->offset
+ part
->size
))
237 blocksize
= mtd
->erasesize
;
238 r_blocksize
= reciprocal_value(blocksize
);
240 if (!(block
= kmalloc(blocksize
, GFP_KERNEL
)))
243 NFLASH_LOCK(mtd
->mlock
);
245 mask
= blocksize
- 1;
246 /* Check and skip bad blocks */
247 blk_offset
= offset
& ~mask
;
248 good_bytes
= part
->offset
& ~mask
;
249 part_blk_start
= reciprocal_divide(good_bytes
, r_blocksize
);
250 part_blk_end
= reciprocal_divide(part
->offset
+ part
->size
, r_blocksize
);
252 for (blk_idx
= part_blk_start
; blk_idx
< part_blk_end
; blk_idx
++) {
253 if (nflash
->map
[blk_idx
] != 0) {
254 skip_bytes
+= blocksize
;
256 if (good_bytes
== blk_offset
)
258 good_bytes
+= blocksize
;
261 if (blk_idx
== part_blk_end
) {
265 blk_offset
= blocksize
* blk_idx
;
266 /* Backup and erase one block at a time */
271 from
= offset
& ~mask
;
272 /* Copy existing data into holding block if necessary */
273 if (((offset
& (blocksize
-1)) != 0) || (len
< blocksize
)) {
274 ret
= _nflash_mtd_read(mtd
, part
, from
, blocksize
,
278 if (read_len
!= blocksize
) {
283 /* Copy input data into holding block */
284 copy_len
= min(len
, blocksize
- (offset
& mask
));
285 memcpy(block
+ (offset
& mask
), ptr
, copy_len
);
287 off
= (uint
) from
+ skip_bytes
;
289 if ((ret
= hndnand_erase(nflash
->nfl
, off
)) < 0) {
290 hndnand_mark_badb(nflash
->nfl
, off
);
291 nflash
->map
[blk_idx
] = 1;
292 skip_bytes
+= blocksize
;
296 /* Write holding block */
298 write_len
= blocksize
;
300 if ((bytes
= hndnand_write(nflash
->nfl
,
301 (uint
) from
+ skip_bytes
, (uint
) write_len
,
302 (uchar
*) write_ptr
)) < 0) {
303 hndnand_mark_badb(nflash
->nfl
, off
);
304 nflash
->map
[blk_idx
] = 1;
305 skip_bytes
+= blocksize
;
321 /* Check and skip bad blocks */
323 blk_offset
+= blocksize
;
325 while ((nflash
->map
[blk_idx
] != 0) &&
326 (blk_offset
< (part
->offset
+part
->size
))) {
327 skip_bytes
+= blocksize
;
328 blk_offset
+= blocksize
;
331 if (blk_offset
>= (part
->offset
+part
->size
)) {
338 NFLASH_UNLOCK(mtd
->mlock
);
346 nflash_mtd_erase(struct mtd_info
*mtd
, struct erase_info
*erase
)
348 struct nflash_mtd
*nflash
= (struct nflash_mtd
*) mtd
->priv
;
349 struct mtd_partition
*part
= NULL
;
351 uint addr
, len
, blocksize
;
352 uint part_start_blk
, part_end_blk
;
353 uint blknum
, new_addr
, erase_blknum
;
354 uint reciprocal_blocksize
;
359 blocksize
= mtd
->erasesize
;
360 reciprocal_blocksize
= reciprocal_value(blocksize
);
362 /* Check address range */
366 if ((addr
+ len
) > mtd
->size
)
369 if (addr
& (blocksize
- 1))
372 /* Locate the part */
373 for (i
= 0; nflash_parts
[i
].name
; i
++) {
374 if (addr
>= nflash_parts
[i
].offset
&&
375 ((addr
+ len
) <= (nflash_parts
[i
].offset
+ nflash_parts
[i
].size
))) {
376 part
= &nflash_parts
[i
];
384 NFLASH_LOCK(mtd
->mlock
);
386 /* Find the effective start block address to erase */
387 part_start_blk
= reciprocal_divide(part
->offset
& ~(blocksize
-1),
388 reciprocal_blocksize
);
389 part_end_blk
= reciprocal_divide(((part
->offset
+ part
->size
) + (blocksize
-1)),
390 reciprocal_blocksize
);
392 new_addr
= part_start_blk
* blocksize
;
393 /* The block number to be skipped relative to the start address of
396 blknum
= reciprocal_divide(addr
- new_addr
, reciprocal_blocksize
);
398 for (i
= part_start_blk
; (i
< part_end_blk
) && (blknum
> 0); i
++) {
399 if (nflash
->map
[i
] != 0) {
400 new_addr
+= blocksize
;
402 new_addr
+= blocksize
;
407 /* Erase the blocks from the new block address */
408 erase_blknum
= reciprocal_divide(len
+ (blocksize
-1), reciprocal_blocksize
);
410 if ((new_addr
+ (erase_blknum
* blocksize
)) > (part
->offset
+ part
->size
)) {
415 for (i
= new_addr
; erase_blknum
; i
+= blocksize
) {
416 /* Skip bad block erase */
417 uint j
= reciprocal_divide(i
, reciprocal_blocksize
);
418 if (nflash
->map
[j
] != 0) {
422 if ((ret
= hndnand_erase(nflash
->nfl
, i
)) < 0) {
423 hndnand_mark_badb(nflash
->nfl
, i
);
424 nflash
->map
[i
/ blocksize
] = 1;
431 /* Set erase status */
433 erase
->state
= MTD_ERASE_FAILED
;
435 erase
->state
= MTD_ERASE_DONE
;
437 NFLASH_UNLOCK(mtd
->mlock
);
439 /* Call erase callback */
441 erase
->callback(erase
);
446 #if LINUX_VERSION_CODE < 0x20212 && defined(MODULE)
447 #define nflash_mtd_init init_module
448 #define nflash_mtd_exit cleanup_module
452 nflash_mtd_init(void)
456 #ifdef CONFIG_MTD_PARTITIONS
457 struct mtd_partition
*parts
;
461 memset(&nflash
, 0, sizeof(struct nflash_mtd
));
463 /* attach to the backplane */
464 if (!(nflash
.sih
= si_kattach(SI_OSH
))) {
465 printk(KERN_ERR
"nflash: error attaching to backplane\n");
470 /* Initialize serial flash access */
471 if (!(info
= hndnand_init(nflash
.sih
))) {
472 printk(KERN_ERR
"nflash: found no supported devices\n");
478 /* Setup region info */
479 nflash
.region
.offset
= 0;
480 nflash
.region
.erasesize
= info
->blocksize
;
481 nflash
.region
.numblocks
= info
->numblocks
;
482 if (nflash
.region
.erasesize
> nflash
.mtd
.erasesize
)
483 nflash
.mtd
.erasesize
= nflash
.region
.erasesize
;
484 /* At most 2GB is supported */
485 nflash
.mtd
.size
= (info
->size
>= (1 << 11)) ? (1 << 31) : (info
->size
<< 20);
486 nflash
.mtd
.numeraseregions
= 1;
487 nflash
.map
= (unsigned char *)kmalloc(info
->numblocks
, GFP_KERNEL
);
489 memset(nflash
.map
, 0, info
->numblocks
);
491 /* Register with MTD */
492 nflash
.mtd
.name
= "nflash";
493 nflash
.mtd
.type
= MTD_NANDFLASH
;
494 nflash
.mtd
.flags
= MTD_CAP_NANDFLASH
;
495 nflash
.mtd
.eraseregions
= &nflash
.region
;
496 nflash
.mtd
.erase
= nflash_mtd_erase
;
497 nflash
.mtd
.read
= nflash_mtd_read
;
498 nflash
.mtd
.write
= nflash_mtd_write
;
499 nflash
.mtd
.writesize
= NFL_SECTOR_SIZE
;
500 nflash
.mtd
.priv
= &nflash
;
501 nflash
.mtd
.owner
= THIS_MODULE
;
502 nflash
.mtd
.mlock
= partitions_lock_init();
503 if (!nflash
.mtd
.mlock
)
507 NFLASH_LOCK(nflash
.mtd
.mlock
);
508 for (i
= 0; i
< info
->numblocks
; i
++) {
509 if (hndnand_checkbadb(nflash
.nfl
, (i
* info
->blocksize
)) != 0) {
513 NFLASH_UNLOCK(nflash
.mtd
.mlock
);
515 #ifdef CONFIG_MTD_PARTITIONS
516 parts
= init_nflash_mtd_partitions(info
, &nflash
.mtd
, nflash
.mtd
.size
);
520 for (i
= 0; parts
[i
].name
; i
++)
523 ret
= add_mtd_partitions(&nflash
.mtd
, parts
, i
);
525 printk(KERN_ERR
"nflash: add_mtd failed\n");
528 nflash_parts
= parts
;
537 nflash_mtd_exit(void)
539 #ifdef CONFIG_MTD_PARTITIONS
540 del_mtd_partitions(&nflash
.mtd
);
542 del_mtd_device(&nflash
.mtd
);
546 module_init(nflash_mtd_init
);
547 module_exit(nflash_mtd_exit
);