jz4725b_bch.c (7319B)
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * JZ4725B BCH controller driver 4 * 5 * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net> 6 * 7 * Based on jz4780_bch.c 8 */ 9 10#include <linux/bitops.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_BHCSR 0x4 23#define BCH_BHCCR 0x8 24#define BCH_BHCNT 0xc 25#define BCH_BHDR 0x10 26#define BCH_BHPAR0 0x14 27#define BCH_BHERR0 0x28 28#define BCH_BHINT 0x24 29#define BCH_BHINTES 0x3c 30#define BCH_BHINTEC 0x40 31#define BCH_BHINTE 0x38 32 33#define BCH_BHCR_ENCE BIT(3) 34#define BCH_BHCR_BSEL BIT(2) 35#define BCH_BHCR_INIT BIT(1) 36#define BCH_BHCR_BCHE BIT(0) 37 38#define BCH_BHCNT_DEC_COUNT_SHIFT 16 39#define BCH_BHCNT_DEC_COUNT_MASK (0x3ff << BCH_BHCNT_DEC_COUNT_SHIFT) 40#define BCH_BHCNT_ENC_COUNT_SHIFT 0 41#define BCH_BHCNT_ENC_COUNT_MASK (0x3ff << BCH_BHCNT_ENC_COUNT_SHIFT) 42 43#define BCH_BHERR_INDEX0_SHIFT 0 44#define BCH_BHERR_INDEX0_MASK (0x1fff << BCH_BHERR_INDEX0_SHIFT) 45#define BCH_BHERR_INDEX1_SHIFT 16 46#define BCH_BHERR_INDEX1_MASK (0x1fff << BCH_BHERR_INDEX1_SHIFT) 47 48#define BCH_BHINT_ERRC_SHIFT 28 49#define BCH_BHINT_ERRC_MASK (0xf << 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_ALL_0 BIT(5) 53#define BCH_BHINT_ALL_F BIT(4) 54#define BCH_BHINT_DECF BIT(3) 55#define BCH_BHINT_ENCF BIT(2) 56#define BCH_BHINT_UNCOR BIT(1) 57#define BCH_BHINT_ERR BIT(0) 58 59/* Timeout for BCH calculation/correction. */ 60#define BCH_TIMEOUT_US 100000 61 62static inline void jz4725b_bch_config_set(struct ingenic_ecc *bch, u32 cfg) 63{ 64 writel(cfg, bch->base + BCH_BHCSR); 65} 66 67static inline void jz4725b_bch_config_clear(struct ingenic_ecc *bch, u32 cfg) 68{ 69 writel(cfg, bch->base + BCH_BHCCR); 70} 71 72static int jz4725b_bch_reset(struct ingenic_ecc *bch, 73 struct ingenic_ecc_params *params, bool calc_ecc) 74{ 75 u32 reg, max_value; 76 77 /* Clear interrupt status. */ 78 writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT); 79 80 /* Initialise and enable BCH. */ 81 jz4725b_bch_config_clear(bch, 0x1f); 82 jz4725b_bch_config_set(bch, BCH_BHCR_BCHE); 83 84 if (params->strength == 8) 85 jz4725b_bch_config_set(bch, BCH_BHCR_BSEL); 86 else 87 jz4725b_bch_config_clear(bch, BCH_BHCR_BSEL); 88 89 if (calc_ecc) /* calculate ECC from data */ 90 jz4725b_bch_config_set(bch, BCH_BHCR_ENCE); 91 else /* correct data from ECC */ 92 jz4725b_bch_config_clear(bch, BCH_BHCR_ENCE); 93 94 jz4725b_bch_config_set(bch, BCH_BHCR_INIT); 95 96 max_value = BCH_BHCNT_ENC_COUNT_MASK >> BCH_BHCNT_ENC_COUNT_SHIFT; 97 if (params->size > max_value) 98 return -EINVAL; 99 100 max_value = BCH_BHCNT_DEC_COUNT_MASK >> BCH_BHCNT_DEC_COUNT_SHIFT; 101 if (params->size + params->bytes > max_value) 102 return -EINVAL; 103 104 /* Set up BCH count register. */ 105 reg = params->size << BCH_BHCNT_ENC_COUNT_SHIFT; 106 reg |= (params->size + params->bytes) << BCH_BHCNT_DEC_COUNT_SHIFT; 107 writel(reg, bch->base + BCH_BHCNT); 108 109 return 0; 110} 111 112static void jz4725b_bch_disable(struct ingenic_ecc *bch) 113{ 114 /* Clear interrupts */ 115 writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT); 116 117 /* Disable the hardware */ 118 jz4725b_bch_config_clear(bch, BCH_BHCR_BCHE); 119} 120 121static void jz4725b_bch_write_data(struct ingenic_ecc *bch, const u8 *buf, 122 size_t size) 123{ 124 while (size--) 125 writeb(*buf++, bch->base + BCH_BHDR); 126} 127 128static void jz4725b_bch_read_parity(struct ingenic_ecc *bch, u8 *buf, 129 size_t size) 130{ 131 size_t size32 = size / sizeof(u32); 132 size_t size8 = size % sizeof(u32); 133 u32 *dest32; 134 u8 *dest8; 135 u32 val, offset = 0; 136 137 dest32 = (u32 *)buf; 138 while (size32--) { 139 *dest32++ = readl_relaxed(bch->base + BCH_BHPAR0 + offset); 140 offset += sizeof(u32); 141 } 142 143 dest8 = (u8 *)dest32; 144 val = readl_relaxed(bch->base + BCH_BHPAR0 + offset); 145 switch (size8) { 146 case 3: 147 dest8[2] = (val >> 16) & 0xff; 148 fallthrough; 149 case 2: 150 dest8[1] = (val >> 8) & 0xff; 151 fallthrough; 152 case 1: 153 dest8[0] = val & 0xff; 154 break; 155 } 156} 157 158static int jz4725b_bch_wait_complete(struct ingenic_ecc *bch, unsigned int irq, 159 u32 *status) 160{ 161 u32 reg; 162 int ret; 163 164 /* 165 * While we could use interrupts here and sleep until the operation 166 * completes, the controller works fairly quickly (usually a few 167 * microseconds) and so the overhead of sleeping until we get an 168 * interrupt quite noticeably decreases performance. 169 */ 170 ret = readl_relaxed_poll_timeout(bch->base + BCH_BHINT, reg, 171 reg & irq, 0, BCH_TIMEOUT_US); 172 if (ret) 173 return ret; 174 175 if (status) 176 *status = reg; 177 178 writel(reg, bch->base + BCH_BHINT); 179 180 return 0; 181} 182 183static int jz4725b_calculate(struct ingenic_ecc *bch, 184 struct ingenic_ecc_params *params, 185 const u8 *buf, u8 *ecc_code) 186{ 187 int ret; 188 189 mutex_lock(&bch->lock); 190 191 ret = jz4725b_bch_reset(bch, params, true); 192 if (ret) { 193 dev_err(bch->dev, "Unable to init BCH with given parameters\n"); 194 goto out_disable; 195 } 196 197 jz4725b_bch_write_data(bch, buf, params->size); 198 199 ret = jz4725b_bch_wait_complete(bch, BCH_BHINT_ENCF, NULL); 200 if (ret) { 201 dev_err(bch->dev, "timed out while calculating ECC\n"); 202 goto out_disable; 203 } 204 205 jz4725b_bch_read_parity(bch, ecc_code, params->bytes); 206 207out_disable: 208 jz4725b_bch_disable(bch); 209 mutex_unlock(&bch->lock); 210 211 return ret; 212} 213 214static int jz4725b_correct(struct ingenic_ecc *bch, 215 struct ingenic_ecc_params *params, 216 u8 *buf, u8 *ecc_code) 217{ 218 u32 reg, errors, bit; 219 unsigned int i; 220 int ret; 221 222 mutex_lock(&bch->lock); 223 224 ret = jz4725b_bch_reset(bch, params, false); 225 if (ret) { 226 dev_err(bch->dev, "Unable to init BCH with given parameters\n"); 227 goto out; 228 } 229 230 jz4725b_bch_write_data(bch, buf, params->size); 231 jz4725b_bch_write_data(bch, ecc_code, params->bytes); 232 233 ret = jz4725b_bch_wait_complete(bch, BCH_BHINT_DECF, ®); 234 if (ret) { 235 dev_err(bch->dev, "timed out while correcting data\n"); 236 goto out; 237 } 238 239 if (reg & (BCH_BHINT_ALL_F | BCH_BHINT_ALL_0)) { 240 /* Data and ECC is all 0xff or 0x00 - nothing to correct */ 241 ret = 0; 242 goto out; 243 } 244 245 if (reg & BCH_BHINT_UNCOR) { 246 /* Uncorrectable ECC error */ 247 ret = -EBADMSG; 248 goto out; 249 } 250 251 errors = (reg & BCH_BHINT_ERRC_MASK) >> BCH_BHINT_ERRC_SHIFT; 252 253 /* Correct any detected errors. */ 254 for (i = 0; i < errors; i++) { 255 if (i & 1) { 256 bit = (reg & BCH_BHERR_INDEX1_MASK) >> BCH_BHERR_INDEX1_SHIFT; 257 } else { 258 reg = readl(bch->base + BCH_BHERR0 + (i * 4)); 259 bit = (reg & BCH_BHERR_INDEX0_MASK) >> BCH_BHERR_INDEX0_SHIFT; 260 } 261 262 buf[(bit >> 3)] ^= BIT(bit & 0x7); 263 } 264 265out: 266 jz4725b_bch_disable(bch); 267 mutex_unlock(&bch->lock); 268 269 return ret; 270} 271 272static const struct ingenic_ecc_ops jz4725b_bch_ops = { 273 .disable = jz4725b_bch_disable, 274 .calculate = jz4725b_calculate, 275 .correct = jz4725b_correct, 276}; 277 278static const struct of_device_id jz4725b_bch_dt_match[] = { 279 { .compatible = "ingenic,jz4725b-bch", .data = &jz4725b_bch_ops }, 280 {}, 281}; 282MODULE_DEVICE_TABLE(of, jz4725b_bch_dt_match); 283 284static struct platform_driver jz4725b_bch_driver = { 285 .probe = ingenic_ecc_probe, 286 .driver = { 287 .name = "jz4725b-bch", 288 .of_match_table = jz4725b_bch_dt_match, 289 }, 290}; 291module_platform_driver(jz4725b_bch_driver); 292 293MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 294MODULE_DESCRIPTION("Ingenic JZ4725B BCH controller driver"); 295MODULE_LICENSE("GPL v2");