1*a8374683SAleksei Sviridkin // SPDX-License-Identifier: GPL-2.0 2*a8374683SAleksei Sviridkin /* 3*a8374683SAleksei Sviridkin * Authors: 4*a8374683SAleksei Sviridkin * Andrey Zolotarev <andrey.zolotarev@keenetic.com> - the main driver logic 5*a8374683SAleksei Sviridkin * Aleksei Sviridkin <f@lex.la> - adaptation to the mainline Linux kernel 6*a8374683SAleksei Sviridkin * 7*a8374683SAleksei Sviridkin * Based on: 8*a8374683SAleksei Sviridkin * https://github.com/keenetic/kernel-49/commit/bacade569fb12bc0ad31ba09bca9b890118fbca7 9*a8374683SAleksei Sviridkin */ 10*a8374683SAleksei Sviridkin 11*a8374683SAleksei Sviridkin #include <linux/device.h> 12*a8374683SAleksei Sviridkin #include <linux/kernel.h> 13*a8374683SAleksei Sviridkin #include <linux/mtd/spinand.h> 14*a8374683SAleksei Sviridkin 15*a8374683SAleksei Sviridkin #define SPINAND_MFR_HEYANGTEK 0xc9 16*a8374683SAleksei Sviridkin 17*a8374683SAleksei Sviridkin #define HYF1GQ4_STATUS_ECC_LIMIT_BITFLIPS (3 << 4) 18*a8374683SAleksei Sviridkin 19*a8374683SAleksei Sviridkin static SPINAND_OP_VARIANTS(read_cache_variants, 20*a8374683SAleksei Sviridkin SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 1, NULL, 0, 0), 21*a8374683SAleksei Sviridkin SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0), 22*a8374683SAleksei Sviridkin SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0), 23*a8374683SAleksei Sviridkin SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0), 24*a8374683SAleksei Sviridkin SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0), 25*a8374683SAleksei Sviridkin SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0)); 26*a8374683SAleksei Sviridkin 27*a8374683SAleksei Sviridkin static SPINAND_OP_VARIANTS(write_cache_variants, 28*a8374683SAleksei Sviridkin SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0), 29*a8374683SAleksei Sviridkin SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0)); 30*a8374683SAleksei Sviridkin 31*a8374683SAleksei Sviridkin static SPINAND_OP_VARIANTS(update_cache_variants, 32*a8374683SAleksei Sviridkin SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0), 33*a8374683SAleksei Sviridkin SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0)); 34*a8374683SAleksei Sviridkin 35*a8374683SAleksei Sviridkin /* 36*a8374683SAleksei Sviridkin * HYF1GQ4UDACAE is a GD5F1GQ4-compatible die, so the OOB layout is taken 37*a8374683SAleksei Sviridkin * from gd5fxgq4xa: the on-die ECC parity occupies bytes 8..15 of each 38*a8374683SAleksei Sviridkin * 16-byte section, the bad block marker sits in byte 0 and the remaining 39*a8374683SAleksei Sviridkin * bytes are exposed as free. 40*a8374683SAleksei Sviridkin */ 41*a8374683SAleksei Sviridkin static int hyf1gq4_ooblayout_ecc(struct mtd_info *mtd, int section, 42*a8374683SAleksei Sviridkin struct mtd_oob_region *region) 43*a8374683SAleksei Sviridkin { 44*a8374683SAleksei Sviridkin if (section > 3) 45*a8374683SAleksei Sviridkin return -ERANGE; 46*a8374683SAleksei Sviridkin 47*a8374683SAleksei Sviridkin region->offset = (16 * section) + 8; 48*a8374683SAleksei Sviridkin region->length = 8; 49*a8374683SAleksei Sviridkin 50*a8374683SAleksei Sviridkin return 0; 51*a8374683SAleksei Sviridkin } 52*a8374683SAleksei Sviridkin 53*a8374683SAleksei Sviridkin static int hyf1gq4_ooblayout_free(struct mtd_info *mtd, int section, 54*a8374683SAleksei Sviridkin struct mtd_oob_region *region) 55*a8374683SAleksei Sviridkin { 56*a8374683SAleksei Sviridkin if (section > 3) 57*a8374683SAleksei Sviridkin return -ERANGE; 58*a8374683SAleksei Sviridkin 59*a8374683SAleksei Sviridkin if (section) { 60*a8374683SAleksei Sviridkin region->offset = 16 * section; 61*a8374683SAleksei Sviridkin region->length = 8; 62*a8374683SAleksei Sviridkin } else { 63*a8374683SAleksei Sviridkin /* section 0 has one byte reserved for the bad block marker */ 64*a8374683SAleksei Sviridkin region->offset = 1; 65*a8374683SAleksei Sviridkin region->length = 7; 66*a8374683SAleksei Sviridkin } 67*a8374683SAleksei Sviridkin 68*a8374683SAleksei Sviridkin return 0; 69*a8374683SAleksei Sviridkin } 70*a8374683SAleksei Sviridkin 71*a8374683SAleksei Sviridkin static const struct mtd_ooblayout_ops hyf1gq4_ooblayout = { 72*a8374683SAleksei Sviridkin .ecc = hyf1gq4_ooblayout_ecc, 73*a8374683SAleksei Sviridkin .free = hyf1gq4_ooblayout_free, 74*a8374683SAleksei Sviridkin }; 75*a8374683SAleksei Sviridkin 76*a8374683SAleksei Sviridkin static int hyf1gq4_ecc_get_status(struct spinand_device *spinand, u8 status) 77*a8374683SAleksei Sviridkin { 78*a8374683SAleksei Sviridkin struct nand_device *nand = spinand_to_nand(spinand); 79*a8374683SAleksei Sviridkin 80*a8374683SAleksei Sviridkin switch (status & STATUS_ECC_MASK) { 81*a8374683SAleksei Sviridkin case STATUS_ECC_NO_BITFLIPS: 82*a8374683SAleksei Sviridkin return 0; 83*a8374683SAleksei Sviridkin 84*a8374683SAleksei Sviridkin case STATUS_ECC_UNCOR_ERROR: 85*a8374683SAleksei Sviridkin return -EBADMSG; 86*a8374683SAleksei Sviridkin 87*a8374683SAleksei Sviridkin case STATUS_ECC_HAS_BITFLIPS: 88*a8374683SAleksei Sviridkin /* 89*a8374683SAleksei Sviridkin * The die exposes only a coarse 2-bit ECC status and has no 90*a8374683SAleksei Sviridkin * register for the exact bitflip count. This code means 91*a8374683SAleksei Sviridkin * "corrected, below the refresh threshold", so report half of 92*a8374683SAleksei Sviridkin * the ECC strength as a representative value. 93*a8374683SAleksei Sviridkin */ 94*a8374683SAleksei Sviridkin return nanddev_get_ecc_conf(nand)->strength / 2; 95*a8374683SAleksei Sviridkin 96*a8374683SAleksei Sviridkin case HYF1GQ4_STATUS_ECC_LIMIT_BITFLIPS: 97*a8374683SAleksei Sviridkin /* 98*a8374683SAleksei Sviridkin * "Corrected, refresh recommended": report the full ECC 99*a8374683SAleksei Sviridkin * strength so the upper layers relocate the data. 100*a8374683SAleksei Sviridkin */ 101*a8374683SAleksei Sviridkin return nanddev_get_ecc_conf(nand)->strength; 102*a8374683SAleksei Sviridkin 103*a8374683SAleksei Sviridkin default: 104*a8374683SAleksei Sviridkin break; 105*a8374683SAleksei Sviridkin } 106*a8374683SAleksei Sviridkin 107*a8374683SAleksei Sviridkin return -EINVAL; 108*a8374683SAleksei Sviridkin } 109*a8374683SAleksei Sviridkin 110*a8374683SAleksei Sviridkin static const struct spinand_info heyangtek_spinand_table[] = { 111*a8374683SAleksei Sviridkin SPINAND_INFO("HYF1GQ4UDACAE", 112*a8374683SAleksei Sviridkin SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x21), 113*a8374683SAleksei Sviridkin NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), 114*a8374683SAleksei Sviridkin NAND_ECCREQ(4, 512), 115*a8374683SAleksei Sviridkin SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 116*a8374683SAleksei Sviridkin &write_cache_variants, 117*a8374683SAleksei Sviridkin &update_cache_variants), 118*a8374683SAleksei Sviridkin SPINAND_HAS_QE_BIT, 119*a8374683SAleksei Sviridkin SPINAND_ECCINFO(&hyf1gq4_ooblayout, 120*a8374683SAleksei Sviridkin hyf1gq4_ecc_get_status)), 121*a8374683SAleksei Sviridkin }; 122*a8374683SAleksei Sviridkin 123*a8374683SAleksei Sviridkin static const struct spinand_manufacturer_ops heyangtek_spinand_manuf_ops = { 124*a8374683SAleksei Sviridkin }; 125*a8374683SAleksei Sviridkin 126*a8374683SAleksei Sviridkin const struct spinand_manufacturer heyangtek_spinand_manufacturer = { 127*a8374683SAleksei Sviridkin .id = SPINAND_MFR_HEYANGTEK, 128*a8374683SAleksei Sviridkin .name = "HeYangTek", 129*a8374683SAleksei Sviridkin .chips = heyangtek_spinand_table, 130*a8374683SAleksei Sviridkin .nchips = ARRAY_SIZE(heyangtek_spinand_table), 131*a8374683SAleksei Sviridkin .ops = &heyangtek_spinand_manuf_ops, 132*a8374683SAleksei Sviridkin }; 133