jz4780_bch.c (6776B)
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 62static 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 83static 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 89static 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 106static 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 136static 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 160static 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 183static 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 226out: 227 jz4780_bch_disable(bch); 228 mutex_unlock(&bch->lock); 229 return ret; 230} 231 232static 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 247static const struct ingenic_ecc_ops jz4780_bch_ops = { 248 .disable = jz4780_bch_disable, 249 .calculate = jz4780_calculate, 250 .correct = jz4780_correct, 251}; 252 253static const struct of_device_id jz4780_bch_dt_match[] = { 254 { .compatible = "ingenic,jz4780-bch", .data = &jz4780_bch_ops }, 255 {}, 256}; 257MODULE_DEVICE_TABLE(of, jz4780_bch_dt_match); 258 259static struct platform_driver jz4780_bch_driver = { 260 .probe = jz4780_bch_probe, 261 .driver = { 262 .name = "jz4780-bch", 263 .of_match_table = jz4780_bch_dt_match, 264 }, 265}; 266module_platform_driver(jz4780_bch_driver); 267 268MODULE_AUTHOR("Alex Smith <alex@alex-smith.me.uk>"); 269MODULE_AUTHOR("Harvey Hunt <harveyhuntnexus@gmail.com>"); 270MODULE_DESCRIPTION("Ingenic JZ4780 BCH error correction driver"); 271MODULE_LICENSE("GPL v2");