296 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * JZ4725B BCH controller driver4 *5 * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>6 *7 * Based on jz4780_bch.c8 */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 0x022#define BCH_BHCSR 0x423#define BCH_BHCCR 0x824#define BCH_BHCNT 0xc25#define BCH_BHDR 0x1026#define BCH_BHPAR0 0x1427#define BCH_BHERR0 0x2828#define BCH_BHINT 0x2429#define BCH_BHINTES 0x3c30#define BCH_BHINTEC 0x4031#define BCH_BHINTE 0x3832 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 1639#define BCH_BHCNT_DEC_COUNT_MASK (0x3ff << BCH_BHCNT_DEC_COUNT_SHIFT)40#define BCH_BHCNT_ENC_COUNT_SHIFT 041#define BCH_BHCNT_ENC_COUNT_MASK (0x3ff << BCH_BHCNT_ENC_COUNT_SHIFT)42 43#define BCH_BHERR_INDEX0_SHIFT 044#define BCH_BHERR_INDEX0_MASK (0x1fff << BCH_BHERR_INDEX0_SHIFT)45#define BCH_BHERR_INDEX1_SHIFT 1646#define BCH_BHERR_INDEX1_MASK (0x1fff << BCH_BHERR_INDEX1_SHIFT)47 48#define BCH_BHINT_ERRC_SHIFT 2849#define BCH_BHINT_ERRC_MASK (0xf << BCH_BHINT_ERRC_SHIFT)50#define BCH_BHINT_TERRC_SHIFT 1651#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 10000061 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 else87 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 operation166 * completes, the controller works fairly quickly (usually a few167 * microseconds) and so the overhead of sleeping until we get an168 * 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");296