diff options
Diffstat (limited to 'drivers/mtd/nand/atmel_nand.c')
-rw-r--r-- | drivers/mtd/nand/atmel_nand.c | 81 |
1 files changed, 77 insertions, 4 deletions
diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c index 19d1e9d17bf9..5c1423a2ffb5 100644 --- a/drivers/mtd/nand/atmel_nand.c +++ b/drivers/mtd/nand/atmel_nand.c @@ -127,6 +127,7 @@ struct atmel_nand_host { bool has_pmecc; u8 pmecc_corr_cap; u16 pmecc_sector_size; + bool has_no_lookup_table; u32 pmecc_lookup_table_offset; u32 pmecc_lookup_table_offset_512; u32 pmecc_lookup_table_offset_1024; @@ -1112,12 +1113,66 @@ static int pmecc_choose_ecc(struct atmel_nand_host *host, return 0; } +static inline int deg(unsigned int poly) +{ + /* polynomial degree is the most-significant bit index */ + return fls(poly) - 1; +} + +static int build_gf_tables(int mm, unsigned int poly, + int16_t *index_of, int16_t *alpha_to) +{ + unsigned int i, x = 1; + const unsigned int k = 1 << deg(poly); + unsigned int nn = (1 << mm) - 1; + + /* primitive polynomial must be of degree m */ + if (k != (1u << mm)) + return -EINVAL; + + for (i = 0; i < nn; i++) { + alpha_to[i] = x; + index_of[x] = i; + if (i && (x == 1)) + /* polynomial is not primitive (a^i=1 with 0<i<2^m-1) */ + return -EINVAL; + x <<= 1; + if (x & k) + x ^= poly; + } + alpha_to[nn] = 1; + index_of[0] = 0; + + return 0; +} + +static uint16_t *create_lookup_table(struct device *dev, int sector_size) +{ + int degree = (sector_size == 512) ? + PMECC_GF_DIMENSION_13 : + PMECC_GF_DIMENSION_14; + unsigned int poly = (sector_size == 512) ? + PMECC_GF_13_PRIMITIVE_POLY : + PMECC_GF_14_PRIMITIVE_POLY; + int table_size = (sector_size == 512) ? + PMECC_LOOKUP_TABLE_SIZE_512 : + PMECC_LOOKUP_TABLE_SIZE_1024; + + int16_t *addr = devm_kzalloc(dev, 2 * table_size * sizeof(uint16_t), + GFP_KERNEL); + if (addr && build_gf_tables(degree, poly, addr, addr + table_size)) + return NULL; + + return addr; +} + static int atmel_pmecc_nand_init_params(struct platform_device *pdev, struct atmel_nand_host *host) { struct mtd_info *mtd = &host->mtd; struct nand_chip *nand_chip = &host->nand_chip; struct resource *regs, *regs_pmerr, *regs_rom; + uint16_t *galois_table; int cap, sector_size, err_no; err_no = pmecc_choose_ecc(host, &cap, §or_size); @@ -1163,8 +1218,24 @@ static int atmel_pmecc_nand_init_params(struct platform_device *pdev, regs_rom = platform_get_resource(pdev, IORESOURCE_MEM, 3); host->pmecc_rom_base = devm_ioremap_resource(&pdev->dev, regs_rom); if (IS_ERR(host->pmecc_rom_base)) { - err_no = PTR_ERR(host->pmecc_rom_base); - goto err; + if (!host->has_no_lookup_table) + /* Don't display the information again */ + dev_err(host->dev, "Can not get I/O resource for ROM, will build a lookup table in runtime!\n"); + + host->has_no_lookup_table = true; + } + + if (host->has_no_lookup_table) { + /* Build the look-up table in runtime */ + galois_table = create_lookup_table(host->dev, sector_size); + if (!galois_table) { + dev_err(host->dev, "Failed to build a lookup table in runtime!\n"); + err_no = -EINVAL; + goto err; + } + + host->pmecc_rom_base = (void __iomem *)galois_table; + host->pmecc_lookup_table_offset = 0; } nand_chip->ecc.size = sector_size; @@ -1501,8 +1572,10 @@ static int atmel_of_init_port(struct atmel_nand_host *host, if (of_property_read_u32_array(np, "atmel,pmecc-lookup-table-offset", offset, 2) != 0) { - dev_err(host->dev, "Cannot get PMECC lookup table offset\n"); - return -EINVAL; + dev_err(host->dev, "Cannot get PMECC lookup table offset, will build a lookup table in runtime.\n"); + host->has_no_lookup_table = true; + /* Will build a lookup table and initialize the offset later */ + return 0; } if (!offset[0] && !offset[1]) { dev_err(host->dev, "Invalid PMECC lookup table offset\n"); |