linux/drivers/mtd/nand/raw/ingenic/jz4725b_bch.c
<<
>>
Prefs
   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, &reg);
 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