linux/drivers/mtd/nand/raw/jz4780_bch.c
<<
>>
Prefs
   1/*
   2 * JZ4780 BCH controller
   3 *
   4 * Copyright (c) 2015 Imagination Technologies
   5 * Author: Alex Smith <alex.smith@imgtec.com>
   6 *
   7 * This program is free software; you can redistribute it and/or modify it
   8 * under the terms of the GNU General Public License version 2 as published
   9 * by the Free Software Foundation.
  10 */
  11
  12#include <linux/bitops.h>
  13#include <linux/clk.h>
  14#include <linux/delay.h>
  15#include <linux/init.h>
  16#include <linux/iopoll.h>
  17#include <linux/module.h>
  18#include <linux/mutex.h>
  19#include <linux/of.h>
  20#include <linux/of_platform.h>
  21#include <linux/platform_device.h>
  22#include <linux/sched.h>
  23#include <linux/slab.h>
  24
  25#include "jz4780_bch.h"
  26
  27#define BCH_BHCR                        0x0
  28#define BCH_BHCCR                       0x8
  29#define BCH_BHCNT                       0xc
  30#define BCH_BHDR                        0x10
  31#define BCH_BHPAR0                      0x14
  32#define BCH_BHERR0                      0x84
  33#define BCH_BHINT                       0x184
  34#define BCH_BHINTES                     0x188
  35#define BCH_BHINTEC                     0x18c
  36#define BCH_BHINTE                      0x190
  37
  38#define BCH_BHCR_BSEL_SHIFT             4
  39#define BCH_BHCR_BSEL_MASK              (0x7f << BCH_BHCR_BSEL_SHIFT)
  40#define BCH_BHCR_ENCE                   BIT(2)
  41#define BCH_BHCR_INIT                   BIT(1)
  42#define BCH_BHCR_BCHE                   BIT(0)
  43
  44#define BCH_BHCNT_PARITYSIZE_SHIFT      16
  45#define BCH_BHCNT_PARITYSIZE_MASK       (0x7f << BCH_BHCNT_PARITYSIZE_SHIFT)
  46#define BCH_BHCNT_BLOCKSIZE_SHIFT       0
  47#define BCH_BHCNT_BLOCKSIZE_MASK        (0x7ff << BCH_BHCNT_BLOCKSIZE_SHIFT)
  48
  49#define BCH_BHERR_MASK_SHIFT            16
  50#define BCH_BHERR_MASK_MASK             (0xffff << BCH_BHERR_MASK_SHIFT)
  51#define BCH_BHERR_INDEX_SHIFT           0
  52#define BCH_BHERR_INDEX_MASK            (0x7ff << BCH_BHERR_INDEX_SHIFT)
  53
  54#define BCH_BHINT_ERRC_SHIFT            24
  55#define BCH_BHINT_ERRC_MASK             (0x7f << BCH_BHINT_ERRC_SHIFT)
  56#define BCH_BHINT_TERRC_SHIFT           16
  57#define BCH_BHINT_TERRC_MASK            (0x7f << BCH_BHINT_TERRC_SHIFT)
  58#define BCH_BHINT_DECF                  BIT(3)
  59#define BCH_BHINT_ENCF                  BIT(2)
  60#define BCH_BHINT_UNCOR                 BIT(1)
  61#define BCH_BHINT_ERR                   BIT(0)
  62
  63#define BCH_CLK_RATE                    (200 * 1000 * 1000)
  64
  65/* Timeout for BCH calculation/correction. */
  66#define BCH_TIMEOUT_US                  100000
  67
  68struct jz4780_bch {
  69        struct device *dev;
  70        void __iomem *base;
  71        struct clk *clk;
  72        struct mutex lock;
  73};
  74
  75static void jz4780_bch_init(struct jz4780_bch *bch,
  76                            struct jz4780_bch_params *params, bool encode)
  77{
  78        u32 reg;
  79
  80        /* Clear interrupt status. */
  81        writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT);
  82
  83        /* Set up BCH count register. */
  84        reg = params->size << BCH_BHCNT_BLOCKSIZE_SHIFT;
  85        reg |= params->bytes << BCH_BHCNT_PARITYSIZE_SHIFT;
  86        writel(reg, bch->base + BCH_BHCNT);
  87
  88        /* Initialise and enable BCH. */
  89        reg = BCH_BHCR_BCHE | BCH_BHCR_INIT;
  90        reg |= params->strength << BCH_BHCR_BSEL_SHIFT;
  91        if (encode)
  92                reg |= BCH_BHCR_ENCE;
  93        writel(reg, bch->base + BCH_BHCR);
  94}
  95
  96static void jz4780_bch_disable(struct jz4780_bch *bch)
  97{
  98        writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT);
  99        writel(BCH_BHCR_BCHE, bch->base + BCH_BHCCR);
 100}
 101
 102static void jz4780_bch_write_data(struct jz4780_bch *bch, const void *buf,
 103                                  size_t size)
 104{
 105        size_t size32 = size / sizeof(u32);
 106        size_t size8 = size % sizeof(u32);
 107        const u32 *src32;
 108        const u8 *src8;
 109
 110        src32 = (const u32 *)buf;
 111        while (size32--)
 112                writel(*src32++, bch->base + BCH_BHDR);
 113
 114        src8 = (const u8 *)src32;
 115        while (size8--)
 116                writeb(*src8++, bch->base + BCH_BHDR);
 117}
 118
 119static void jz4780_bch_read_parity(struct jz4780_bch *bch, void *buf,
 120                                   size_t size)
 121{
 122        size_t size32 = size / sizeof(u32);
 123        size_t size8 = size % sizeof(u32);
 124        u32 *dest32;
 125        u8 *dest8;
 126        u32 val, offset = 0;
 127
 128        dest32 = (u32 *)buf;
 129        while (size32--) {
 130                *dest32++ = readl(bch->base + BCH_BHPAR0 + offset);
 131                offset += sizeof(u32);
 132        }
 133
 134        dest8 = (u8 *)dest32;
 135        val = readl(bch->base + BCH_BHPAR0 + offset);
 136        switch (size8) {
 137        case 3:
 138                dest8[2] = (val >> 16) & 0xff;
 139                /* fall through */
 140        case 2:
 141                dest8[1] = (val >> 8) & 0xff;
 142                /* fall through */
 143        case 1:
 144                dest8[0] = val & 0xff;
 145                break;
 146        }
 147}
 148
 149static bool jz4780_bch_wait_complete(struct jz4780_bch *bch, unsigned int irq,
 150                                     u32 *status)
 151{
 152        u32 reg;
 153        int ret;
 154
 155        /*
 156         * While we could use interrupts here and sleep until the operation
 157         * completes, the controller works fairly quickly (usually a few
 158         * microseconds) and so the overhead of sleeping until we get an
 159         * interrupt quite noticeably decreases performance.
 160         */
 161        ret = readl_poll_timeout(bch->base + BCH_BHINT, reg,
 162                                 (reg & irq) == irq, 0, BCH_TIMEOUT_US);
 163        if (ret)
 164                return false;
 165
 166        if (status)
 167                *status = reg;
 168
 169        writel(reg, bch->base + BCH_BHINT);
 170        return true;
 171}
 172
 173/**
 174 * jz4780_bch_calculate() - calculate ECC for a data buffer
 175 * @bch: BCH device.
 176 * @params: BCH parameters.
 177 * @buf: input buffer with raw data.
 178 * @ecc_code: output buffer with ECC.
 179 *
 180 * Return: 0 on success, -ETIMEDOUT if timed out while waiting for BCH
 181 * controller.
 182 */
 183int jz4780_bch_calculate(struct jz4780_bch *bch, struct jz4780_bch_params *params,
 184                         const u8 *buf, u8 *ecc_code)
 185{
 186        int ret = 0;
 187
 188        mutex_lock(&bch->lock);
 189        jz4780_bch_init(bch, params, true);
 190        jz4780_bch_write_data(bch, buf, params->size);
 191
 192        if (jz4780_bch_wait_complete(bch, BCH_BHINT_ENCF, NULL)) {
 193                jz4780_bch_read_parity(bch, ecc_code, params->bytes);
 194        } else {
 195                dev_err(bch->dev, "timed out while calculating ECC\n");
 196                ret = -ETIMEDOUT;
 197        }
 198
 199        jz4780_bch_disable(bch);
 200        mutex_unlock(&bch->lock);
 201        return ret;
 202}
 203EXPORT_SYMBOL(jz4780_bch_calculate);
 204
 205/**
 206 * jz4780_bch_correct() - detect and correct bit errors
 207 * @bch: BCH device.
 208 * @params: BCH parameters.
 209 * @buf: raw data read from the chip.
 210 * @ecc_code: ECC read from the chip.
 211 *
 212 * Given the raw data and the ECC read from the NAND device, detects and
 213 * corrects errors in the data.
 214 *
 215 * Return: the number of bit errors corrected, -EBADMSG if there are too many
 216 * errors to correct or -ETIMEDOUT if we timed out waiting for the controller.
 217 */
 218int jz4780_bch_correct(struct jz4780_bch *bch, struct jz4780_bch_params *params,
 219                       u8 *buf, u8 *ecc_code)
 220{
 221        u32 reg, mask, index;
 222        int i, ret, count;
 223
 224        mutex_lock(&bch->lock);
 225
 226        jz4780_bch_init(bch, params, false);
 227        jz4780_bch_write_data(bch, buf, params->size);
 228        jz4780_bch_write_data(bch, ecc_code, params->bytes);
 229
 230        if (!jz4780_bch_wait_complete(bch, BCH_BHINT_DECF, &reg)) {
 231                dev_err(bch->dev, "timed out while correcting data\n");
 232                ret = -ETIMEDOUT;
 233                goto out;
 234        }
 235
 236        if (reg & BCH_BHINT_UNCOR) {
 237                dev_warn(bch->dev, "uncorrectable ECC error\n");
 238                ret = -EBADMSG;
 239                goto out;
 240        }
 241
 242        /* Correct any detected errors. */
 243        if (reg & BCH_BHINT_ERR) {
 244                count = (reg & BCH_BHINT_ERRC_MASK) >> BCH_BHINT_ERRC_SHIFT;
 245                ret = (reg & BCH_BHINT_TERRC_MASK) >> BCH_BHINT_TERRC_SHIFT;
 246
 247                for (i = 0; i < count; i++) {
 248                        reg = readl(bch->base + BCH_BHERR0 + (i * 4));
 249                        mask = (reg & BCH_BHERR_MASK_MASK) >>
 250                                                BCH_BHERR_MASK_SHIFT;
 251                        index = (reg & BCH_BHERR_INDEX_MASK) >>
 252                                                BCH_BHERR_INDEX_SHIFT;
 253                        buf[(index * 2) + 0] ^= mask;
 254                        buf[(index * 2) + 1] ^= mask >> 8;
 255                }
 256        } else {
 257                ret = 0;
 258        }
 259
 260out:
 261        jz4780_bch_disable(bch);
 262        mutex_unlock(&bch->lock);
 263        return ret;
 264}
 265EXPORT_SYMBOL(jz4780_bch_correct);
 266
 267/**
 268 * jz4780_bch_get() - get the BCH controller device
 269 * @np: BCH device tree node.
 270 *
 271 * Gets the BCH controller device from the specified device tree node. The
 272 * device must be released with jz4780_bch_release() when it is no longer being
 273 * used.
 274 *
 275 * Return: a pointer to jz4780_bch, errors are encoded into the pointer.
 276 * PTR_ERR(-EPROBE_DEFER) if the device hasn't been initialised yet.
 277 */
 278static struct jz4780_bch *jz4780_bch_get(struct device_node *np)
 279{
 280        struct platform_device *pdev;
 281        struct jz4780_bch *bch;
 282
 283        pdev = of_find_device_by_node(np);
 284        if (!pdev || !platform_get_drvdata(pdev))
 285                return ERR_PTR(-EPROBE_DEFER);
 286
 287        get_device(&pdev->dev);
 288
 289        bch = platform_get_drvdata(pdev);
 290        clk_prepare_enable(bch->clk);
 291
 292        return bch;
 293}
 294
 295/**
 296 * of_jz4780_bch_get() - get the BCH controller from a DT node
 297 * @of_node: the node that contains a bch-controller property.
 298 *
 299 * Get the bch-controller property from the given device tree
 300 * node and pass it to jz4780_bch_get to do the work.
 301 *
 302 * Return: a pointer to jz4780_bch, errors are encoded into the pointer.
 303 * PTR_ERR(-EPROBE_DEFER) if the device hasn't been initialised yet.
 304 */
 305struct jz4780_bch *of_jz4780_bch_get(struct device_node *of_node)
 306{
 307        struct jz4780_bch *bch = NULL;
 308        struct device_node *np;
 309
 310        np = of_parse_phandle(of_node, "ingenic,bch-controller", 0);
 311
 312        if (np) {
 313                bch = jz4780_bch_get(np);
 314                of_node_put(np);
 315        }
 316        return bch;
 317}
 318EXPORT_SYMBOL(of_jz4780_bch_get);
 319
 320/**
 321 * jz4780_bch_release() - release the BCH controller device
 322 * @bch: BCH device.
 323 */
 324void jz4780_bch_release(struct jz4780_bch *bch)
 325{
 326        clk_disable_unprepare(bch->clk);
 327        put_device(bch->dev);
 328}
 329EXPORT_SYMBOL(jz4780_bch_release);
 330
 331static int jz4780_bch_probe(struct platform_device *pdev)
 332{
 333        struct device *dev = &pdev->dev;
 334        struct jz4780_bch *bch;
 335        struct resource *res;
 336
 337        bch = devm_kzalloc(dev, sizeof(*bch), GFP_KERNEL);
 338        if (!bch)
 339                return -ENOMEM;
 340
 341        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 342        bch->base = devm_ioremap_resource(dev, res);
 343        if (IS_ERR(bch->base))
 344                return PTR_ERR(bch->base);
 345
 346        jz4780_bch_disable(bch);
 347
 348        bch->clk = devm_clk_get(dev, NULL);
 349        if (IS_ERR(bch->clk)) {
 350                dev_err(dev, "failed to get clock: %ld\n", PTR_ERR(bch->clk));
 351                return PTR_ERR(bch->clk);
 352        }
 353
 354        clk_set_rate(bch->clk, BCH_CLK_RATE);
 355
 356        mutex_init(&bch->lock);
 357
 358        bch->dev = dev;
 359        platform_set_drvdata(pdev, bch);
 360
 361        return 0;
 362}
 363
 364static const struct of_device_id jz4780_bch_dt_match[] = {
 365        { .compatible = "ingenic,jz4780-bch" },
 366        {},
 367};
 368MODULE_DEVICE_TABLE(of, jz4780_bch_dt_match);
 369
 370static struct platform_driver jz4780_bch_driver = {
 371        .probe          = jz4780_bch_probe,
 372        .driver = {
 373                .name   = "jz4780-bch",
 374                .of_match_table = of_match_ptr(jz4780_bch_dt_match),
 375        },
 376};
 377module_platform_driver(jz4780_bch_driver);
 378
 379MODULE_AUTHOR("Alex Smith <alex@alex-smith.me.uk>");
 380MODULE_AUTHOR("Harvey Hunt <harveyhuntnexus@gmail.com>");
 381MODULE_DESCRIPTION("Ingenic JZ4780 BCH error correction driver");
 382MODULE_LICENSE("GPL v2");
 383