162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright © 2009 - Maxim Levitsky 462306a36Sopenharmony_ci * Common routines & support for xD format 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci#include <linux/kernel.h> 762306a36Sopenharmony_ci#include <linux/mtd/rawnand.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/sizes.h> 1062306a36Sopenharmony_ci#include "sm_common.h" 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_cistatic int oob_sm_ooblayout_ecc(struct mtd_info *mtd, int section, 1362306a36Sopenharmony_ci struct mtd_oob_region *oobregion) 1462306a36Sopenharmony_ci{ 1562306a36Sopenharmony_ci if (section > 1) 1662306a36Sopenharmony_ci return -ERANGE; 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci oobregion->length = 3; 1962306a36Sopenharmony_ci oobregion->offset = ((section + 1) * 8) - 3; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci return 0; 2262306a36Sopenharmony_ci} 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_cistatic int oob_sm_ooblayout_free(struct mtd_info *mtd, int section, 2562306a36Sopenharmony_ci struct mtd_oob_region *oobregion) 2662306a36Sopenharmony_ci{ 2762306a36Sopenharmony_ci switch (section) { 2862306a36Sopenharmony_ci case 0: 2962306a36Sopenharmony_ci /* reserved */ 3062306a36Sopenharmony_ci oobregion->offset = 0; 3162306a36Sopenharmony_ci oobregion->length = 4; 3262306a36Sopenharmony_ci break; 3362306a36Sopenharmony_ci case 1: 3462306a36Sopenharmony_ci /* LBA1 */ 3562306a36Sopenharmony_ci oobregion->offset = 6; 3662306a36Sopenharmony_ci oobregion->length = 2; 3762306a36Sopenharmony_ci break; 3862306a36Sopenharmony_ci case 2: 3962306a36Sopenharmony_ci /* LBA2 */ 4062306a36Sopenharmony_ci oobregion->offset = 11; 4162306a36Sopenharmony_ci oobregion->length = 2; 4262306a36Sopenharmony_ci break; 4362306a36Sopenharmony_ci default: 4462306a36Sopenharmony_ci return -ERANGE; 4562306a36Sopenharmony_ci } 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci return 0; 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic const struct mtd_ooblayout_ops oob_sm_ops = { 5162306a36Sopenharmony_ci .ecc = oob_sm_ooblayout_ecc, 5262306a36Sopenharmony_ci .free = oob_sm_ooblayout_free, 5362306a36Sopenharmony_ci}; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci/* NOTE: This layout is not compatabable with SmartMedia, */ 5662306a36Sopenharmony_ci/* because the 256 byte devices have page depenent oob layout */ 5762306a36Sopenharmony_ci/* However it does preserve the bad block markers */ 5862306a36Sopenharmony_ci/* If you use smftl, it will bypass this and work correctly */ 5962306a36Sopenharmony_ci/* If you not, then you break SmartMedia compliance anyway */ 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic int oob_sm_small_ooblayout_ecc(struct mtd_info *mtd, int section, 6262306a36Sopenharmony_ci struct mtd_oob_region *oobregion) 6362306a36Sopenharmony_ci{ 6462306a36Sopenharmony_ci if (section) 6562306a36Sopenharmony_ci return -ERANGE; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci oobregion->length = 3; 6862306a36Sopenharmony_ci oobregion->offset = 0; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci return 0; 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic int oob_sm_small_ooblayout_free(struct mtd_info *mtd, int section, 7462306a36Sopenharmony_ci struct mtd_oob_region *oobregion) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci switch (section) { 7762306a36Sopenharmony_ci case 0: 7862306a36Sopenharmony_ci /* reserved */ 7962306a36Sopenharmony_ci oobregion->offset = 3; 8062306a36Sopenharmony_ci oobregion->length = 2; 8162306a36Sopenharmony_ci break; 8262306a36Sopenharmony_ci case 1: 8362306a36Sopenharmony_ci /* LBA1 */ 8462306a36Sopenharmony_ci oobregion->offset = 6; 8562306a36Sopenharmony_ci oobregion->length = 2; 8662306a36Sopenharmony_ci break; 8762306a36Sopenharmony_ci default: 8862306a36Sopenharmony_ci return -ERANGE; 8962306a36Sopenharmony_ci } 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci return 0; 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_cistatic const struct mtd_ooblayout_ops oob_sm_small_ops = { 9562306a36Sopenharmony_ci .ecc = oob_sm_small_ooblayout_ecc, 9662306a36Sopenharmony_ci .free = oob_sm_small_ooblayout_free, 9762306a36Sopenharmony_ci}; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistatic int sm_block_markbad(struct nand_chip *chip, loff_t ofs) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 10262306a36Sopenharmony_ci struct mtd_oob_ops ops = { }; 10362306a36Sopenharmony_ci struct sm_oob oob; 10462306a36Sopenharmony_ci int ret; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci memset(&oob, -1, SM_OOB_SIZE); 10762306a36Sopenharmony_ci oob.block_status = 0x0F; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci /* As long as this function is called on erase block boundaries 11062306a36Sopenharmony_ci it will work correctly for 256 byte nand */ 11162306a36Sopenharmony_ci ops.mode = MTD_OPS_PLACE_OOB; 11262306a36Sopenharmony_ci ops.ooboffs = 0; 11362306a36Sopenharmony_ci ops.ooblen = mtd->oobsize; 11462306a36Sopenharmony_ci ops.oobbuf = (void *)&oob; 11562306a36Sopenharmony_ci ops.datbuf = NULL; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci ret = mtd_write_oob(mtd, ofs, &ops); 11962306a36Sopenharmony_ci if (ret < 0 || ops.oobretlen != SM_OOB_SIZE) { 12062306a36Sopenharmony_ci pr_notice("sm_common: can't mark sector at %i as bad\n", 12162306a36Sopenharmony_ci (int)ofs); 12262306a36Sopenharmony_ci return -EIO; 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci return 0; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic struct nand_flash_dev nand_smartmedia_flash_ids[] = { 12962306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 2MiB 3,3V ROM", 0x5d, 2, SZ_8K, NAND_ROM), 13062306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 4MiB 3,3V", 0xe3, 4, SZ_8K, 0), 13162306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 4MiB 3,3/5V", 0xe5, 4, SZ_8K, 0), 13262306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 4MiB 5V", 0x6b, 4, SZ_8K, 0), 13362306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 4MiB 3,3V ROM", 0xd5, 4, SZ_8K, NAND_ROM), 13462306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 8MiB 3,3V", 0xe6, 8, SZ_8K, 0), 13562306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 8MiB 3,3V ROM", 0xd6, 8, SZ_8K, NAND_ROM), 13662306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 16MiB 3,3V", 0x73, 16, SZ_16K, 0), 13762306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 16MiB 3,3V ROM", 0x57, 16, SZ_16K, NAND_ROM), 13862306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 32MiB 3,3V", 0x75, 32, SZ_16K, 0), 13962306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 32MiB 3,3V ROM", 0x58, 32, SZ_16K, NAND_ROM), 14062306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 64MiB 3,3V", 0x76, 64, SZ_16K, 0), 14162306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 64MiB 3,3V ROM", 0xd9, 64, SZ_16K, NAND_ROM), 14262306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 128MiB 3,3V", 0x79, 128, SZ_16K, 0), 14362306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 128MiB 3,3V ROM", 0xda, 128, SZ_16K, NAND_ROM), 14462306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 256MiB 3, 3V", 0x71, 256, SZ_16K, 0), 14562306a36Sopenharmony_ci LEGACY_ID_NAND("SmartMedia 256MiB 3,3V ROM", 0x5b, 256, SZ_16K, NAND_ROM), 14662306a36Sopenharmony_ci {NULL} 14762306a36Sopenharmony_ci}; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic struct nand_flash_dev nand_xd_flash_ids[] = { 15062306a36Sopenharmony_ci LEGACY_ID_NAND("xD 16MiB 3,3V", 0x73, 16, SZ_16K, 0), 15162306a36Sopenharmony_ci LEGACY_ID_NAND("xD 32MiB 3,3V", 0x75, 32, SZ_16K, 0), 15262306a36Sopenharmony_ci LEGACY_ID_NAND("xD 64MiB 3,3V", 0x76, 64, SZ_16K, 0), 15362306a36Sopenharmony_ci LEGACY_ID_NAND("xD 128MiB 3,3V", 0x79, 128, SZ_16K, 0), 15462306a36Sopenharmony_ci LEGACY_ID_NAND("xD 256MiB 3,3V", 0x71, 256, SZ_16K, NAND_BROKEN_XD), 15562306a36Sopenharmony_ci LEGACY_ID_NAND("xD 512MiB 3,3V", 0xdc, 512, SZ_16K, NAND_BROKEN_XD), 15662306a36Sopenharmony_ci LEGACY_ID_NAND("xD 1GiB 3,3V", 0xd3, 1024, SZ_16K, NAND_BROKEN_XD), 15762306a36Sopenharmony_ci LEGACY_ID_NAND("xD 2GiB 3,3V", 0xd5, 2048, SZ_16K, NAND_BROKEN_XD), 15862306a36Sopenharmony_ci {NULL} 15962306a36Sopenharmony_ci}; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic int sm_attach_chip(struct nand_chip *chip) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci /* Bad block marker position */ 16662306a36Sopenharmony_ci chip->badblockpos = 0x05; 16762306a36Sopenharmony_ci chip->badblockbits = 7; 16862306a36Sopenharmony_ci chip->legacy.block_markbad = sm_block_markbad; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci /* ECC layout */ 17162306a36Sopenharmony_ci if (mtd->writesize == SM_SECTOR_SIZE) 17262306a36Sopenharmony_ci mtd_set_ooblayout(mtd, &oob_sm_ops); 17362306a36Sopenharmony_ci else if (mtd->writesize == SM_SMALL_PAGE) 17462306a36Sopenharmony_ci mtd_set_ooblayout(mtd, &oob_sm_small_ops); 17562306a36Sopenharmony_ci else 17662306a36Sopenharmony_ci return -ENODEV; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci return 0; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic const struct nand_controller_ops sm_controller_ops = { 18262306a36Sopenharmony_ci .attach_chip = sm_attach_chip, 18362306a36Sopenharmony_ci}; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ciint sm_register_device(struct mtd_info *mtd, int smartmedia) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci struct nand_chip *chip = mtd_to_nand(mtd); 18862306a36Sopenharmony_ci struct nand_flash_dev *flash_ids; 18962306a36Sopenharmony_ci int ret; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci chip->options |= NAND_SKIP_BBTSCAN; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci /* Scan for card properties */ 19462306a36Sopenharmony_ci chip->legacy.dummy_controller.ops = &sm_controller_ops; 19562306a36Sopenharmony_ci flash_ids = smartmedia ? nand_smartmedia_flash_ids : nand_xd_flash_ids; 19662306a36Sopenharmony_ci ret = nand_scan_with_ids(chip, 1, flash_ids); 19762306a36Sopenharmony_ci if (ret) 19862306a36Sopenharmony_ci return ret; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci ret = mtd_device_register(mtd, NULL, 0); 20162306a36Sopenharmony_ci if (ret) 20262306a36Sopenharmony_ci nand_cleanup(chip); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci return ret; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sm_register_device); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 20962306a36Sopenharmony_ciMODULE_AUTHOR("Maxim Levitsky <maximlevitsky@gmail.com>"); 21062306a36Sopenharmony_ciMODULE_DESCRIPTION("Common SmartMedia/xD functions"); 211