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        case 2:
 140                dest8[1] = (val >> 8) & 0xff;
 141        case 1:
 142                dest8[0] = val & 0xff;
 143                break;
 144        }
 145}
 146
 147static bool jz4780_bch_wait_complete(struct jz4780_bch *bch, unsigned int irq,
 148                                     u32 *status)
 149{
 150        u32 reg;
 151        int ret;
 152
 153        /*
 154         * While we could use interrupts here and sleep until the operation
 155         * completes, the controller works fairly quickly (usually a few
 156         * microseconds) and so the overhead of sleeping until we get an
 157         * interrupt quite noticeably decreases performance.
 158         */
 159        ret = readl_poll_timeout(bch->base + BCH_BHINT, reg,
 160                                 (reg & irq) == irq, 0, BCH_TIMEOUT_US);
 161        if (ret)
 162                return false;
 163
 164        if (status)
 165                *status = reg;
 166
 167        writel(reg, bch->base + BCH_BHINT);
 168        return true;
 169}
 170
 171/**
 172 * jz4780_bch_calculate() - calculate ECC for a data buffer
 173 * @bch: BCH device.
 174 * @params: BCH parameters.
 175 * @buf: input buffer with raw data.
 176 * @ecc_code: output buffer with ECC.
 177 *
 178 * Return: 0 on success, -ETIMEDOUT if timed out while waiting for BCH
 179 * controller.
 180 */
 181int jz4780_bch_calculate(struct jz4780_bch *bch, struct jz4780_bch_params *params,
 182                         const u8 *buf, u8 *ecc_code)
 183{
 184        int ret = 0;
 185
 186        mutex_lock(&bch->lock);
 187        jz4780_bch_init(bch, params, true);
 188        jz4780_bch_write_data(bch, buf, params->size);
 189
 190        if (jz4780_bch_wait_complete(bch, BCH_BHINT_ENCF, NULL)) {
 191                jz4780_bch_read_parity(bch, ecc_code, params->bytes);
 192        } else {
 193                dev_err(bch->dev, "timed out while calculating ECC\n");
 194                ret = -ETIMEDOUT;
 195        }
 196
 197        jz4780_bch_disable(bch);
 198        mutex_unlock(&bch->lock);
 199        return ret;
 200}
 201EXPORT_SYMBOL(jz4780_bch_calculate);
 202
 203/**
 204 * jz4780_bch_correct() - detect and correct bit errors
 205 * @bch: BCH device.
 206 * @params: BCH parameters.
 207 * @buf: raw data read from the chip.
 208 * @ecc_code: ECC read from the chip.
 209 *
 210 * Given the raw data and the ECC read from the NAND device, detects and
 211 * corrects errors in the data.
 212 *
 213 * Return: the number of bit errors corrected, -EBADMSG if there are too many
 214 * errors to correct or -ETIMEDOUT if we timed out waiting for the controller.
 215 */
 216int jz4780_bch_correct(struct jz4780_bch *bch, struct jz4780_bch_params *params,
 217                       u8 *buf, u8 *ecc_code)
 218{
 219        u32 reg, mask, index;
 220        int i, ret, count;
 221
 222        mutex_lock(&bch->lock);
 223
 224        jz4780_bch_init(bch, params, false);
 225        jz4780_bch_write_data(bch, buf, params->size);
 226        jz4780_bch_write_data(bch, ecc_code, params->bytes);
 227
 228        if (!jz4780_bch_wait_complete(bch, BCH_BHINT_DECF, &reg)) {
 229                dev_err(bch->dev, "timed out while correcting data\n");
 230                ret = -ETIMEDOUT;
 231                goto out;
 232        }
 233
 234        if (reg & BCH_BHINT_UNCOR) {
 235                dev_warn(bch->dev, "uncorrectable ECC error\n");
 236                ret = -EBADMSG;
 237                goto out;
 238        }
 239
 240        /* Correct any detected errors. */
 241        if (reg & BCH_BHINT_ERR) {
 242                count = (reg & BCH_BHINT_ERRC_MASK) >> BCH_BHINT_ERRC_SHIFT;
 243                ret = (reg & BCH_BHINT_TERRC_MASK) >> BCH_BHINT_TERRC_SHIFT;
 244
 245                for (i = 0; i < count; i++) {
 246                        reg = readl(bch->base + BCH_BHERR0 + (i * 4));
 247                        mask = (reg & BCH_BHERR_MASK_MASK) >>
 248                                                BCH_BHERR_MASK_SHIFT;
 249                        index = (reg & BCH_BHERR_INDEX_MASK) >>
 250                                                BCH_BHERR_INDEX_SHIFT;
 251                        buf[(index * 2) + 0] ^= mask;
 252                        buf[(index * 2) + 1] ^= mask >> 8;
 253                }
 254        } else {
 255                ret = 0;
 256        }
 257
 258out:
 259        jz4780_bch_disable(bch);
 260        mutex_unlock(&bch->lock);
 261        return ret;
 262}
 263EXPORT_SYMBOL(jz4780_bch_correct);
 264
 265/**
 266 * jz4780_bch_get() - get the BCH controller device
 267 * @np: BCH device tree node.
 268 *
 269 * Gets the BCH controller device from the specified device tree node. The
 270 * device must be released with jz4780_bch_release() when it is no longer being
 271 * used.
 272 *
 273 * Return: a pointer to jz4780_bch, errors are encoded into the pointer.
 274 * PTR_ERR(-EPROBE_DEFER) if the device hasn't been initialised yet.
 275 */
 276static struct jz4780_bch *jz4780_bch_get(struct device_node *np)
 277{
 278        struct platform_device *pdev;
 279        struct jz4780_bch *bch;
 280
 281        pdev = of_find_device_by_node(np);
 282        if (!pdev || !platform_get_drvdata(pdev))
 283                return ERR_PTR(-EPROBE_DEFER);
 284
 285        get_device(&pdev->dev);
 286
 287        bch = platform_get_drvdata(pdev);
 288        clk_prepare_enable(bch->clk);
 289
 290        return bch;
 291}
 292
 293/**
 294 * of_jz4780_bch_get() - get the BCH controller from a DT node
 295 * @of_node: the node that contains a bch-controller property.
 296 *
 297 * Get the bch-controller property from the given device tree
 298 * node and pass it to jz4780_bch_get to do the work.
 299 *
 300 * Return: a pointer to jz4780_bch, errors are encoded into the pointer.
 301 * PTR_ERR(-EPROBE_DEFER) if the device hasn't been initialised yet.
 302 */
 303struct jz4780_bch *of_jz4780_bch_get(struct device_node *of_node)
 304{
 305        struct jz4780_bch *bch = NULL;
 306        struct device_node *np;
 307
 308        np = of_parse_phandle(of_node, "ingenic,bch-controller", 0);
 309
 310        if (np) {
 311                bch = jz4780_bch_get(np);
 312                of_node_put(np);
 313        }
 314        return bch;
 315}
 316EXPORT_SYMBOL(of_jz4780_bch_get);
 317
 318/**
 319 * jz4780_bch_release() - release the BCH controller device
 320 * @bch: BCH device.
 321 */
 322void jz4780_bch_release(struct jz4780_bch *bch)
 323{
 324        clk_disable_unprepare(bch->clk);
 325        put_device(bch->dev);
 326}
 327EXPORT_SYMBOL(jz4780_bch_release);
 328
 329static int jz4780_bch_probe(struct platform_device *pdev)
 330{
 331        struct device *dev = &pdev->dev;
 332        struct jz4780_bch *bch;
 333        struct resource *res;
 334
 335        bch = devm_kzalloc(dev, sizeof(*bch), GFP_KERNEL);
 336        if (!bch)
 337                return -ENOMEM;
 338
 339        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 340        bch->base = devm_ioremap_resource(dev, res);
 341        if (IS_ERR(bch->base))
 342                return PTR_ERR(bch->base);
 343
 344        jz4780_bch_disable(bch);
 345
 346        bch->clk = devm_clk_get(dev, NULL);
 347        if (IS_ERR(bch->clk)) {
 348                dev_err(dev, "failed to get clock: %ld\n", PTR_ERR(bch->clk));
 349                return PTR_ERR(bch->clk);
 350        }
 351
 352        clk_set_rate(bch->clk, BCH_CLK_RATE);
 353
 354        mutex_init(&bch->lock);
 355
 356        bch->dev = dev;
 357        platform_set_drvdata(pdev, bch);
 358
 359        return 0;
 360}
 361
 362static const struct of_device_id jz4780_bch_dt_match[] = {
 363        { .compatible = "ingenic,jz4780-bch" },
 364        {},
 365};
 366MODULE_DEVICE_TABLE(of, jz4780_bch_dt_match);
 367
 368static struct platform_driver jz4780_bch_driver = {
 369        .probe          = jz4780_bch_probe,
 370        .driver = {
 371                .name   = "jz4780-bch",
 372                .of_match_table = of_match_ptr(jz4780_bch_dt_match),
 373        },
 374};
 375module_platform_driver(jz4780_bch_driver);
 376
 377MODULE_AUTHOR("Alex Smith <alex@alex-smith.me.uk>");
 378MODULE_AUTHOR("Harvey Hunt <harveyhuntnexus@gmail.com>");
 379MODULE_DESCRIPTION("Ingenic JZ4780 BCH error correction driver");
 380MODULE_LICENSE("GPL v2");
 381