Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | // SPDX-License-Identifier: GPL-2.0 /* * JZ4740 ECC controller driver * * Copyright (c) 2019 Paul Cercueil <paul@crapouillou.net> * * based on jz4740-nand.c */ #include <linux/bitops.h> #include <linux/device.h> #include <linux/io.h> #include <linux/module.h> #include <linux/of_platform.h> #include <linux/platform_device.h> #include "ingenic_ecc.h" #define JZ_REG_NAND_ECC_CTRL 0x00 #define JZ_REG_NAND_DATA 0x04 #define JZ_REG_NAND_PAR0 0x08 #define JZ_REG_NAND_PAR1 0x0C #define JZ_REG_NAND_PAR2 0x10 #define JZ_REG_NAND_IRQ_STAT 0x14 #define JZ_REG_NAND_IRQ_CTRL 0x18 #define JZ_REG_NAND_ERR(x) (0x1C + ((x) << 2)) #define JZ_NAND_ECC_CTRL_PAR_READY BIT(4) #define JZ_NAND_ECC_CTRL_ENCODING BIT(3) #define JZ_NAND_ECC_CTRL_RS BIT(2) #define JZ_NAND_ECC_CTRL_RESET BIT(1) #define JZ_NAND_ECC_CTRL_ENABLE BIT(0) #define JZ_NAND_STATUS_ERR_COUNT (BIT(31) | BIT(30) | BIT(29)) #define JZ_NAND_STATUS_PAD_FINISH BIT(4) #define JZ_NAND_STATUS_DEC_FINISH BIT(3) #define JZ_NAND_STATUS_ENC_FINISH BIT(2) #define JZ_NAND_STATUS_UNCOR_ERROR BIT(1) #define JZ_NAND_STATUS_ERROR BIT(0) static const uint8_t empty_block_ecc[] = { 0xcd, 0x9d, 0x90, 0x58, 0xf4, 0x8b, 0xff, 0xb7, 0x6f }; static void jz4740_ecc_reset(struct ingenic_ecc *ecc, bool calc_ecc) { uint32_t reg; /* Clear interrupt status */ writel(0, ecc->base + JZ_REG_NAND_IRQ_STAT); /* Initialize and enable ECC hardware */ reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); reg |= JZ_NAND_ECC_CTRL_RESET; reg |= JZ_NAND_ECC_CTRL_ENABLE; reg |= JZ_NAND_ECC_CTRL_RS; if (calc_ecc) /* calculate ECC from data */ reg |= JZ_NAND_ECC_CTRL_ENCODING; else /* correct data from ECC */ reg &= ~JZ_NAND_ECC_CTRL_ENCODING; writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); } static int jz4740_ecc_calculate(struct ingenic_ecc *ecc, struct ingenic_ecc_params *params, const u8 *buf, u8 *ecc_code) { uint32_t reg, status; unsigned int timeout = 1000; int i; jz4740_ecc_reset(ecc, true); do { status = readl(ecc->base + JZ_REG_NAND_IRQ_STAT); } while (!(status & JZ_NAND_STATUS_ENC_FINISH) && --timeout); if (timeout == 0) return -ETIMEDOUT; reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); reg &= ~JZ_NAND_ECC_CTRL_ENABLE; writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); for (i = 0; i < params->bytes; ++i) ecc_code[i] = readb(ecc->base + JZ_REG_NAND_PAR0 + i); /* * If the written data is completely 0xff, we also want to write 0xff as * ECC, otherwise we will get in trouble when doing subpage writes. */ if (memcmp(ecc_code, empty_block_ecc, sizeof(empty_block_ecc)) == 0) memset(ecc_code, 0xff, sizeof(empty_block_ecc)); return 0; } static void jz_nand_correct_data(uint8_t *buf, int index, int mask) { int offset = index & 0x7; uint16_t data; index += (index >> 3); data = buf[index]; data |= buf[index + 1] << 8; mask ^= (data >> offset) & 0x1ff; data &= ~(0x1ff << offset); data |= (mask << offset); buf[index] = data & 0xff; buf[index + 1] = (data >> 8) & 0xff; } static int jz4740_ecc_correct(struct ingenic_ecc *ecc, struct ingenic_ecc_params *params, u8 *buf, u8 *ecc_code) { int i, error_count, index; uint32_t reg, status, error; unsigned int timeout = 1000; jz4740_ecc_reset(ecc, false); for (i = 0; i < params->bytes; ++i) writeb(ecc_code[i], ecc->base + JZ_REG_NAND_PAR0 + i); reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); reg |= JZ_NAND_ECC_CTRL_PAR_READY; writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); do { status = readl(ecc->base + JZ_REG_NAND_IRQ_STAT); } while (!(status & JZ_NAND_STATUS_DEC_FINISH) && --timeout); if (timeout == 0) return -ETIMEDOUT; reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); reg &= ~JZ_NAND_ECC_CTRL_ENABLE; writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); if (status & JZ_NAND_STATUS_ERROR) { if (status & JZ_NAND_STATUS_UNCOR_ERROR) return -EBADMSG; error_count = (status & JZ_NAND_STATUS_ERR_COUNT) >> 29; for (i = 0; i < error_count; ++i) { error = readl(ecc->base + JZ_REG_NAND_ERR(i)); index = ((error >> 16) & 0x1ff) - 1; if (index >= 0 && index < params->size) jz_nand_correct_data(buf, index, error & 0x1ff); } return error_count; } return 0; } static void jz4740_ecc_disable(struct ingenic_ecc *ecc) { u32 reg; writel(0, ecc->base + JZ_REG_NAND_IRQ_STAT); reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); reg &= ~JZ_NAND_ECC_CTRL_ENABLE; writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); } static const struct ingenic_ecc_ops jz4740_ecc_ops = { .disable = jz4740_ecc_disable, .calculate = jz4740_ecc_calculate, .correct = jz4740_ecc_correct, }; static const struct of_device_id jz4740_ecc_dt_match[] = { { .compatible = "ingenic,jz4740-ecc", .data = &jz4740_ecc_ops }, {}, }; MODULE_DEVICE_TABLE(of, jz4740_ecc_dt_match); static struct platform_driver jz4740_ecc_driver = { .probe = ingenic_ecc_probe, .driver = { .name = "jz4740-ecc", .of_match_table = jz4740_ecc_dt_match, }, }; module_platform_driver(jz4740_ecc_driver); MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); MODULE_DESCRIPTION("Ingenic JZ4740 ECC controller driver"); MODULE_LICENSE("GPL v2"); |