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