|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Author: Mario Kicherer <dev@kicherer.org> | 
|  | */ | 
|  |  | 
|  | #include <linux/device.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/mtd/spinand.h> | 
|  |  | 
|  | #define SPINAND_MFR_ALLIANCEMEMORY	0x52 | 
|  |  | 
|  | #define AM_STATUS_ECC_BITMASK		(3 << 4) | 
|  |  | 
|  | #define AM_STATUS_ECC_NONE_DETECTED	(0 << 4) | 
|  | #define AM_STATUS_ECC_CORRECTED		(1 << 4) | 
|  | #define AM_STATUS_ECC_ERRORED		(2 << 4) | 
|  | #define AM_STATUS_ECC_MAX_CORRECTED	(3 << 4) | 
|  |  | 
|  | static SPINAND_OP_VARIANTS(read_cache_variants, | 
|  | SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0), | 
|  | SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), | 
|  | SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), | 
|  | SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), | 
|  | SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), | 
|  | SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); | 
|  |  | 
|  | static SPINAND_OP_VARIANTS(write_cache_variants, | 
|  | SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), | 
|  | SPINAND_PROG_LOAD(true, 0, NULL, 0)); | 
|  |  | 
|  | static SPINAND_OP_VARIANTS(update_cache_variants, | 
|  | SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), | 
|  | SPINAND_PROG_LOAD(false, 0, NULL, 0)); | 
|  |  | 
|  | static int am_get_eccsize(struct mtd_info *mtd) | 
|  | { | 
|  | if (mtd->oobsize == 64) | 
|  | return 0x20; | 
|  | else if (mtd->oobsize == 128) | 
|  | return 0x38; | 
|  | else if (mtd->oobsize == 256) | 
|  | return 0x70; | 
|  | else | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static int am_ooblayout_ecc(struct mtd_info *mtd, int section, | 
|  | struct mtd_oob_region *region) | 
|  | { | 
|  | int ecc_bytes; | 
|  |  | 
|  | ecc_bytes = am_get_eccsize(mtd); | 
|  | if (ecc_bytes < 0) | 
|  | return ecc_bytes; | 
|  |  | 
|  | region->offset = mtd->oobsize - ecc_bytes; | 
|  | region->length = ecc_bytes; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int am_ooblayout_free(struct mtd_info *mtd, int section, | 
|  | struct mtd_oob_region *region) | 
|  | { | 
|  | int ecc_bytes; | 
|  |  | 
|  | if (section) | 
|  | return -ERANGE; | 
|  |  | 
|  | ecc_bytes = am_get_eccsize(mtd); | 
|  | if (ecc_bytes < 0) | 
|  | return ecc_bytes; | 
|  |  | 
|  | /* | 
|  | * It is unclear how many bytes are used for the bad block marker. We | 
|  | * reserve the common two bytes here. | 
|  | * | 
|  | * The free area in this kind of flash is divided into chunks where the | 
|  | * first 4 bytes of each chunk are unprotected. The number of chunks | 
|  | * depends on the specific model. The models with 4096+256 bytes pages | 
|  | * have 8 chunks, the others 4 chunks. | 
|  | */ | 
|  |  | 
|  | region->offset = 2; | 
|  | region->length = mtd->oobsize - 2 - ecc_bytes; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct mtd_ooblayout_ops am_ooblayout = { | 
|  | .ecc = am_ooblayout_ecc, | 
|  | .free = am_ooblayout_free, | 
|  | }; | 
|  |  | 
|  | static int am_ecc_get_status(struct spinand_device *spinand, u8 status) | 
|  | { | 
|  | switch (status & AM_STATUS_ECC_BITMASK) { | 
|  | case AM_STATUS_ECC_NONE_DETECTED: | 
|  | return 0; | 
|  |  | 
|  | case AM_STATUS_ECC_CORRECTED: | 
|  | /* | 
|  | * use oobsize to determine the flash model and the maximum of | 
|  | * correctable errors and return maximum - 1 by convention | 
|  | */ | 
|  | if (spinand->base.mtd.oobsize == 64) | 
|  | return 3; | 
|  | else | 
|  | return 7; | 
|  |  | 
|  | case AM_STATUS_ECC_ERRORED: | 
|  | return -EBADMSG; | 
|  |  | 
|  | case AM_STATUS_ECC_MAX_CORRECTED: | 
|  | /* | 
|  | * use oobsize to determine the flash model and the maximum of | 
|  | * correctable errors | 
|  | */ | 
|  | if (spinand->base.mtd.oobsize == 64) | 
|  | return 4; | 
|  | else | 
|  | return 8; | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static const struct spinand_info alliancememory_spinand_table[] = { | 
|  | SPINAND_INFO("AS5F34G04SND", | 
|  | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x2f), | 
|  | NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1), | 
|  | NAND_ECCREQ(4, 512), | 
|  | SPINAND_INFO_OP_VARIANTS(&read_cache_variants, | 
|  | &write_cache_variants, | 
|  | &update_cache_variants), | 
|  | SPINAND_HAS_QE_BIT, | 
|  | SPINAND_ECCINFO(&am_ooblayout, | 
|  | am_ecc_get_status)), | 
|  | }; | 
|  |  | 
|  | static const struct spinand_manufacturer_ops alliancememory_spinand_manuf_ops = { | 
|  | }; | 
|  |  | 
|  | const struct spinand_manufacturer alliancememory_spinand_manufacturer = { | 
|  | .id = SPINAND_MFR_ALLIANCEMEMORY, | 
|  | .name = "AllianceMemory", | 
|  | .chips = alliancememory_spinand_table, | 
|  | .nchips = ARRAY_SIZE(alliancememory_spinand_table), | 
|  | .ops = &alliancememory_spinand_manuf_ops, | 
|  | }; |