1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * JZ4780 BCH controller driver 4 * 5 * Copyright (c) 2015 Imagination Technologies 6 * Author: Alex Smith <alex.smith@imgtec.com> 7 */ 8 9 #include <linux/bitops.h> 10 #include <linux/clk.h> 11 #include <linux/device.h> 12 #include <linux/io.h> 13 #include <linux/iopoll.h> 14 #include <linux/module.h> 15 #include <linux/mutex.h> 16 #include <linux/of_platform.h> 17 #include <linux/platform_device.h> 18 19 #include "ingenic_ecc.h" 20 21 #define BCH_BHCR 0x0 22 #define BCH_BHCCR 0x8 23 #define BCH_BHCNT 0xc 24 #define BCH_BHDR 0x10 25 #define BCH_BHPAR0 0x14 26 #define BCH_BHERR0 0x84 27 #define BCH_BHINT 0x184 28 #define BCH_BHINTES 0x188 29 #define BCH_BHINTEC 0x18c 30 #define BCH_BHINTE 0x190 31 32 #define BCH_BHCR_BSEL_SHIFT 4 33 #define BCH_BHCR_BSEL_MASK (0x7f << BCH_BHCR_BSEL_SHIFT) 34 #define BCH_BHCR_ENCE BIT(2) 35 #define BCH_BHCR_INIT BIT(1) 36 #define BCH_BHCR_BCHE BIT(0) 37 38 #define BCH_BHCNT_PARITYSIZE_SHIFT 16 39 #define BCH_BHCNT_PARITYSIZE_MASK (0x7f << BCH_BHCNT_PARITYSIZE_SHIFT) 40 #define BCH_BHCNT_BLOCKSIZE_SHIFT 0 41 #define BCH_BHCNT_BLOCKSIZE_MASK (0x7ff << BCH_BHCNT_BLOCKSIZE_SHIFT) 42 43 #define BCH_BHERR_MASK_SHIFT 16 44 #define BCH_BHERR_MASK_MASK (0xffff << BCH_BHERR_MASK_SHIFT) 45 #define BCH_BHERR_INDEX_SHIFT 0 46 #define BCH_BHERR_INDEX_MASK (0x7ff << BCH_BHERR_INDEX_SHIFT) 47 48 #define BCH_BHINT_ERRC_SHIFT 24 49 #define BCH_BHINT_ERRC_MASK (0x7f << BCH_BHINT_ERRC_SHIFT) 50 #define BCH_BHINT_TERRC_SHIFT 16 51 #define BCH_BHINT_TERRC_MASK (0x7f << BCH_BHINT_TERRC_SHIFT) 52 #define BCH_BHINT_DECF BIT(3) 53 #define BCH_BHINT_ENCF BIT(2) 54 #define BCH_BHINT_UNCOR BIT(1) 55 #define BCH_BHINT_ERR BIT(0) 56 57 #define BCH_CLK_RATE (200 * 1000 * 1000) 58 59 /* Timeout for BCH calculation/correction. */ 60 #define BCH_TIMEOUT_US 100000 61 62 static void jz4780_bch_reset(struct ingenic_ecc *bch, 63 struct ingenic_ecc_params *params, bool encode) 64 { 65 u32 reg; 66 67 /* Clear interrupt status. */ 68 writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT); 69 70 /* Set up BCH count register. */ 71 reg = params->size << BCH_BHCNT_BLOCKSIZE_SHIFT; 72 reg |= params->bytes << BCH_BHCNT_PARITYSIZE_SHIFT; 73 writel(reg, bch->base + BCH_BHCNT); 74 75 /* Initialise and enable BCH. */ 76 reg = BCH_BHCR_BCHE | BCH_BHCR_INIT; 77 reg |= params->strength << BCH_BHCR_BSEL_SHIFT; 78 if (encode) 79 reg |= BCH_BHCR_ENCE; 80 writel(reg, bch->base + BCH_BHCR); 81 } 82 83 static void jz4780_bch_disable(struct ingenic_ecc *bch) 84 { 85 writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT); 86 writel(BCH_BHCR_BCHE, bch->base + BCH_BHCCR); 87 } 88 89 static void jz4780_bch_write_data(struct ingenic_ecc *bch, const void *buf, 90 size_t size) 91 { 92 size_t size32 = size / sizeof(u32); 93 size_t size8 = size % sizeof(u32); 94 const u32 *src32; 95 const u8 *src8; 96 97 src32 = (const u32 *)buf; 98 while (size32--) 99 writel(*src32++, bch->base + BCH_BHDR); 100 101 src8 = (const u8 *)src32; 102 while (size8--) 103 writeb(*src8++, bch->base + BCH_BHDR); 104 } 105 106 static void jz4780_bch_read_parity(struct ingenic_ecc *bch, void *buf, 107 size_t size) 108 { 109 size_t size32 = size / sizeof(u32); 110 size_t size8 = size % sizeof(u32); 111 u32 *dest32; 112 u8 *dest8; 113 u32 val, offset = 0; 114 115 dest32 = (u32 *)buf; 116 while (size32--) { 117 *dest32++ = readl(bch->base + BCH_BHPAR0 + offset); 118 offset += sizeof(u32); 119 } 120 121 dest8 = (u8 *)dest32; 122 val = readl(bch->base + BCH_BHPAR0 + offset); 123 switch (size8) { 124 case 3: 125 dest8[2] = (val >> 16) & 0xff; 126 fallthrough; 127 case 2: 128 dest8[1] = (val >> 8) & 0xff; 129 fallthrough; 130 case 1: 131 dest8[0] = val & 0xff; 132 break; 133 } 134 } 135 136 static bool jz4780_bch_wait_complete(struct ingenic_ecc *bch, unsigned int irq, 137 u32 *status) 138 { 139 u32 reg; 140 int ret; 141 142 /* 143 * While we could use interrupts here and sleep until the operation 144 * completes, the controller works fairly quickly (usually a few 145 * microseconds) and so the overhead of sleeping until we get an 146 * interrupt quite noticeably decreases performance. 147 */ 148 ret = readl_poll_timeout(bch->base + BCH_BHINT, reg, 149 (reg & irq) == irq, 0, BCH_TIMEOUT_US); 150 if (ret) 151 return false; 152 153 if (status) 154 *status = reg; 155 156 writel(reg, bch->base + BCH_BHINT); 157 return true; 158 } 159 160 static int jz4780_calculate(struct ingenic_ecc *bch, 161 struct ingenic_ecc_params *params, 162 const u8 *buf, u8 *ecc_code) 163 { 164 int ret = 0; 165 166 mutex_lock(&bch->lock); 167 168 jz4780_bch_reset(bch, params, true); 169 jz4780_bch_write_data(bch, buf, params->size); 170 171 if (jz4780_bch_wait_complete(bch, BCH_BHINT_ENCF, NULL)) { 172 jz4780_bch_read_parity(bch, ecc_code, params->bytes); 173 } else { 174 dev_err(bch->dev, "timed out while calculating ECC\n"); 175 ret = -ETIMEDOUT; 176 } 177 178 jz4780_bch_disable(bch); 179 mutex_unlock(&bch->lock); 180 return ret; 181 } 182 183 static int jz4780_correct(struct ingenic_ecc *bch, 184 struct ingenic_ecc_params *params, 185 u8 *buf, u8 *ecc_code) 186 { 187 u32 reg, mask, index; 188 int i, ret, count; 189 190 mutex_lock(&bch->lock); 191 192 jz4780_bch_reset(bch, params, false); 193 jz4780_bch_write_data(bch, buf, params->size); 194 jz4780_bch_write_data(bch, ecc_code, params->bytes); 195 196 if (!jz4780_bch_wait_complete(bch, BCH_BHINT_DECF, ®)) { 197 dev_err(bch->dev, "timed out while correcting data\n"); 198 ret = -ETIMEDOUT; 199 goto out; 200 } 201 202 if (reg & BCH_BHINT_UNCOR) { 203 dev_warn(bch->dev, "uncorrectable ECC error\n"); 204 ret = -EBADMSG; 205 goto out; 206 } 207 208 /* Correct any detected errors. */ 209 if (reg & BCH_BHINT_ERR) { 210 count = (reg & BCH_BHINT_ERRC_MASK) >> BCH_BHINT_ERRC_SHIFT; 211 ret = (reg & BCH_BHINT_TERRC_MASK) >> BCH_BHINT_TERRC_SHIFT; 212 213 for (i = 0; i < count; i++) { 214 reg = readl(bch->base + BCH_BHERR0 + (i * 4)); 215 mask = (reg & BCH_BHERR_MASK_MASK) >> 216 BCH_BHERR_MASK_SHIFT; 217 index = (reg & BCH_BHERR_INDEX_MASK) >> 218 BCH_BHERR_INDEX_SHIFT; 219 buf[(index * 2) + 0] ^= mask; 220 buf[(index * 2) + 1] ^= mask >> 8; 221 } 222 } else { 223 ret = 0; 224 } 225 226 out: 227 jz4780_bch_disable(bch); 228 mutex_unlock(&bch->lock); 229 return ret; 230 } 231 232 static int jz4780_bch_probe(struct platform_device *pdev) 233 { 234 struct ingenic_ecc *bch; 235 int ret; 236 237 ret = ingenic_ecc_probe(pdev); 238 if (ret) 239 return ret; 240 241 bch = platform_get_drvdata(pdev); 242 clk_set_rate(bch->clk, BCH_CLK_RATE); 243 244 return 0; 245 } 246 247 static const struct ingenic_ecc_ops jz4780_bch_ops = { 248 .disable = jz4780_bch_disable, 249 .calculate = jz4780_calculate, 250 .correct = jz4780_correct, 251 }; 252 253 static const struct of_device_id jz4780_bch_dt_match[] = { 254 { .compatible = "ingenic,jz4780-bch", .data = &jz4780_bch_ops }, 255 {}, 256 }; 257 MODULE_DEVICE_TABLE(of, jz4780_bch_dt_match); 258 259 static struct platform_driver jz4780_bch_driver = { 260 .probe = jz4780_bch_probe, 261 .driver = { 262 .name = "jz4780-bch", 263 .of_match_table = of_match_ptr(jz4780_bch_dt_match), 264 }, 265 }; 266 module_platform_driver(jz4780_bch_driver); 267 268 MODULE_AUTHOR("Alex Smith <alex@alex-smith.me.uk>"); 269 MODULE_AUTHOR("Harvey Hunt <harveyhuntnexus@gmail.com>"); 270 MODULE_DESCRIPTION("Ingenic JZ4780 BCH error correction driver"); 271 MODULE_LICENSE("GPL v2"); 272