linux/drivers/mtd/nand/raw/omap_elm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Error Location Module
   4 *
   5 * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com/
   6 */
   7
   8#define DRIVER_NAME     "omap-elm"
   9
  10#include <linux/platform_device.h>
  11#include <linux/module.h>
  12#include <linux/interrupt.h>
  13#include <linux/io.h>
  14#include <linux/of.h>
  15#include <linux/sched.h>
  16#include <linux/pm_runtime.h>
  17#include <linux/platform_data/elm.h>
  18
  19#define ELM_SYSCONFIG                   0x010
  20#define ELM_IRQSTATUS                   0x018
  21#define ELM_IRQENABLE                   0x01c
  22#define ELM_LOCATION_CONFIG             0x020
  23#define ELM_PAGE_CTRL                   0x080
  24#define ELM_SYNDROME_FRAGMENT_0         0x400
  25#define ELM_SYNDROME_FRAGMENT_1         0x404
  26#define ELM_SYNDROME_FRAGMENT_2         0x408
  27#define ELM_SYNDROME_FRAGMENT_3         0x40c
  28#define ELM_SYNDROME_FRAGMENT_4         0x410
  29#define ELM_SYNDROME_FRAGMENT_5         0x414
  30#define ELM_SYNDROME_FRAGMENT_6         0x418
  31#define ELM_LOCATION_STATUS             0x800
  32#define ELM_ERROR_LOCATION_0            0x880
  33
  34/* ELM Interrupt Status Register */
  35#define INTR_STATUS_PAGE_VALID          BIT(8)
  36
  37/* ELM Interrupt Enable Register */
  38#define INTR_EN_PAGE_MASK               BIT(8)
  39
  40/* ELM Location Configuration Register */
  41#define ECC_BCH_LEVEL_MASK              0x3
  42
  43/* ELM syndrome */
  44#define ELM_SYNDROME_VALID              BIT(16)
  45
  46/* ELM_LOCATION_STATUS Register */
  47#define ECC_CORRECTABLE_MASK            BIT(8)
  48#define ECC_NB_ERRORS_MASK              0x1f
  49
  50/* ELM_ERROR_LOCATION_0-15 Registers */
  51#define ECC_ERROR_LOCATION_MASK         0x1fff
  52
  53#define ELM_ECC_SIZE                    0x7ff
  54
  55#define SYNDROME_FRAGMENT_REG_SIZE      0x40
  56#define ERROR_LOCATION_SIZE             0x100
  57
  58struct elm_registers {
  59        u32 elm_irqenable;
  60        u32 elm_sysconfig;
  61        u32 elm_location_config;
  62        u32 elm_page_ctrl;
  63        u32 elm_syndrome_fragment_6[ERROR_VECTOR_MAX];
  64        u32 elm_syndrome_fragment_5[ERROR_VECTOR_MAX];
  65        u32 elm_syndrome_fragment_4[ERROR_VECTOR_MAX];
  66        u32 elm_syndrome_fragment_3[ERROR_VECTOR_MAX];
  67        u32 elm_syndrome_fragment_2[ERROR_VECTOR_MAX];
  68        u32 elm_syndrome_fragment_1[ERROR_VECTOR_MAX];
  69        u32 elm_syndrome_fragment_0[ERROR_VECTOR_MAX];
  70};
  71
  72struct elm_info {
  73        struct device *dev;
  74        void __iomem *elm_base;
  75        struct completion elm_completion;
  76        struct list_head list;
  77        enum bch_ecc bch_type;
  78        struct elm_registers elm_regs;
  79        int ecc_steps;
  80        int ecc_syndrome_size;
  81};
  82
  83static LIST_HEAD(elm_devices);
  84
  85static void elm_write_reg(struct elm_info *info, int offset, u32 val)
  86{
  87        writel(val, info->elm_base + offset);
  88}
  89
  90static u32 elm_read_reg(struct elm_info *info, int offset)
  91{
  92        return readl(info->elm_base + offset);
  93}
  94
  95/**
  96 * elm_config - Configure ELM module
  97 * @dev:        ELM device
  98 * @bch_type:   Type of BCH ecc
  99 * @ecc_steps:  ECC steps to assign to config
 100 * @ecc_step_size:      ECC step size to assign to config
 101 * @ecc_syndrome_size:  ECC syndrome size to assign to config
 102 */
 103int elm_config(struct device *dev, enum bch_ecc bch_type,
 104        int ecc_steps, int ecc_step_size, int ecc_syndrome_size)
 105{
 106        u32 reg_val;
 107        struct elm_info *info = dev_get_drvdata(dev);
 108
 109        if (!info) {
 110                dev_err(dev, "Unable to configure elm - device not probed?\n");
 111                return -EPROBE_DEFER;
 112        }
 113        /* ELM cannot detect ECC errors for chunks > 1KB */
 114        if (ecc_step_size > ((ELM_ECC_SIZE + 1) / 2)) {
 115                dev_err(dev, "unsupported config ecc-size=%d\n", ecc_step_size);
 116                return -EINVAL;
 117        }
 118        /* ELM support 8 error syndrome process */
 119        if (ecc_steps > ERROR_VECTOR_MAX && ecc_steps % ERROR_VECTOR_MAX) {
 120                dev_err(dev, "unsupported config ecc-step=%d\n", ecc_steps);
 121                return -EINVAL;
 122        }
 123
 124        reg_val = (bch_type & ECC_BCH_LEVEL_MASK) | (ELM_ECC_SIZE << 16);
 125        elm_write_reg(info, ELM_LOCATION_CONFIG, reg_val);
 126        info->bch_type          = bch_type;
 127        info->ecc_steps         = ecc_steps;
 128        info->ecc_syndrome_size = ecc_syndrome_size;
 129
 130        return 0;
 131}
 132EXPORT_SYMBOL(elm_config);
 133
 134/**
 135 * elm_configure_page_mode - Enable/Disable page mode
 136 * @info:       elm info
 137 * @index:      index number of syndrome fragment vector
 138 * @enable:     enable/disable flag for page mode
 139 *
 140 * Enable page mode for syndrome fragment index
 141 */
 142static void elm_configure_page_mode(struct elm_info *info, int index,
 143                bool enable)
 144{
 145        u32 reg_val;
 146
 147        reg_val = elm_read_reg(info, ELM_PAGE_CTRL);
 148        if (enable)
 149                reg_val |= BIT(index);  /* enable page mode */
 150        else
 151                reg_val &= ~BIT(index); /* disable page mode */
 152
 153        elm_write_reg(info, ELM_PAGE_CTRL, reg_val);
 154}
 155
 156/**
 157 * elm_load_syndrome - Load ELM syndrome reg
 158 * @info:       elm info
 159 * @err_vec:    elm error vectors
 160 * @ecc:        buffer with calculated ecc
 161 *
 162 * Load syndrome fragment registers with calculated ecc in reverse order.
 163 */
 164static void elm_load_syndrome(struct elm_info *info,
 165                struct elm_errorvec *err_vec, u8 *ecc)
 166{
 167        int i, offset;
 168        u32 val;
 169
 170        for (i = 0; i < info->ecc_steps; i++) {
 171
 172                /* Check error reported */
 173                if (err_vec[i].error_reported) {
 174                        elm_configure_page_mode(info, i, true);
 175                        offset = ELM_SYNDROME_FRAGMENT_0 +
 176                                SYNDROME_FRAGMENT_REG_SIZE * i;
 177                        switch (info->bch_type) {
 178                        case BCH8_ECC:
 179                                /* syndrome fragment 0 = ecc[9-12B] */
 180                                val = cpu_to_be32(*(u32 *) &ecc[9]);
 181                                elm_write_reg(info, offset, val);
 182
 183                                /* syndrome fragment 1 = ecc[5-8B] */
 184                                offset += 4;
 185                                val = cpu_to_be32(*(u32 *) &ecc[5]);
 186                                elm_write_reg(info, offset, val);
 187
 188                                /* syndrome fragment 2 = ecc[1-4B] */
 189                                offset += 4;
 190                                val = cpu_to_be32(*(u32 *) &ecc[1]);
 191                                elm_write_reg(info, offset, val);
 192
 193                                /* syndrome fragment 3 = ecc[0B] */
 194                                offset += 4;
 195                                val = ecc[0];
 196                                elm_write_reg(info, offset, val);
 197                                break;
 198                        case BCH4_ECC:
 199                                /* syndrome fragment 0 = ecc[20-52b] bits */
 200                                val = (cpu_to_be32(*(u32 *) &ecc[3]) >> 4) |
 201                                        ((ecc[2] & 0xf) << 28);
 202                                elm_write_reg(info, offset, val);
 203
 204                                /* syndrome fragment 1 = ecc[0-20b] bits */
 205                                offset += 4;
 206                                val = cpu_to_be32(*(u32 *) &ecc[0]) >> 12;
 207                                elm_write_reg(info, offset, val);
 208                                break;
 209                        case BCH16_ECC:
 210                                val = cpu_to_be32(*(u32 *) &ecc[22]);
 211                                elm_write_reg(info, offset, val);
 212                                offset += 4;
 213                                val = cpu_to_be32(*(u32 *) &ecc[18]);
 214                                elm_write_reg(info, offset, val);
 215                                offset += 4;
 216                                val = cpu_to_be32(*(u32 *) &ecc[14]);
 217                                elm_write_reg(info, offset, val);
 218                                offset += 4;
 219                                val = cpu_to_be32(*(u32 *) &ecc[10]);
 220                                elm_write_reg(info, offset, val);
 221                                offset += 4;
 222                                val = cpu_to_be32(*(u32 *) &ecc[6]);
 223                                elm_write_reg(info, offset, val);
 224                                offset += 4;
 225                                val = cpu_to_be32(*(u32 *) &ecc[2]);
 226                                elm_write_reg(info, offset, val);
 227                                offset += 4;
 228                                val = cpu_to_be32(*(u32 *) &ecc[0]) >> 16;
 229                                elm_write_reg(info, offset, val);
 230                                break;
 231                        default:
 232                                pr_err("invalid config bch_type\n");
 233                        }
 234                }
 235
 236                /* Update ecc pointer with ecc byte size */
 237                ecc += info->ecc_syndrome_size;
 238        }
 239}
 240
 241/**
 242 * elm_start_processing - start elm syndrome processing
 243 * @info:       elm info
 244 * @err_vec:    elm error vectors
 245 *
 246 * Set syndrome valid bit for syndrome fragment registers for which
 247 * elm syndrome fragment registers are loaded. This enables elm module
 248 * to start processing syndrome vectors.
 249 */
 250static void elm_start_processing(struct elm_info *info,
 251                struct elm_errorvec *err_vec)
 252{
 253        int i, offset;
 254        u32 reg_val;
 255
 256        /*
 257         * Set syndrome vector valid, so that ELM module
 258         * will process it for vectors error is reported
 259         */
 260        for (i = 0; i < info->ecc_steps; i++) {
 261                if (err_vec[i].error_reported) {
 262                        offset = ELM_SYNDROME_FRAGMENT_6 +
 263                                SYNDROME_FRAGMENT_REG_SIZE * i;
 264                        reg_val = elm_read_reg(info, offset);
 265                        reg_val |= ELM_SYNDROME_VALID;
 266                        elm_write_reg(info, offset, reg_val);
 267                }
 268        }
 269}
 270
 271/**
 272 * elm_error_correction - locate correctable error position
 273 * @info:       elm info
 274 * @err_vec:    elm error vectors
 275 *
 276 * On completion of processing by elm module, error location status
 277 * register updated with correctable/uncorrectable error information.
 278 * In case of correctable errors, number of errors located from
 279 * elm location status register & read the positions from
 280 * elm error location register.
 281 */
 282static void elm_error_correction(struct elm_info *info,
 283                struct elm_errorvec *err_vec)
 284{
 285        int i, j, errors = 0;
 286        int offset;
 287        u32 reg_val;
 288
 289        for (i = 0; i < info->ecc_steps; i++) {
 290
 291                /* Check error reported */
 292                if (err_vec[i].error_reported) {
 293                        offset = ELM_LOCATION_STATUS + ERROR_LOCATION_SIZE * i;
 294                        reg_val = elm_read_reg(info, offset);
 295
 296                        /* Check correctable error or not */
 297                        if (reg_val & ECC_CORRECTABLE_MASK) {
 298                                offset = ELM_ERROR_LOCATION_0 +
 299                                        ERROR_LOCATION_SIZE * i;
 300
 301                                /* Read count of correctable errors */
 302                                err_vec[i].error_count = reg_val &
 303                                        ECC_NB_ERRORS_MASK;
 304
 305                                /* Update the error locations in error vector */
 306                                for (j = 0; j < err_vec[i].error_count; j++) {
 307
 308                                        reg_val = elm_read_reg(info, offset);
 309                                        err_vec[i].error_loc[j] = reg_val &
 310                                                ECC_ERROR_LOCATION_MASK;
 311
 312                                        /* Update error location register */
 313                                        offset += 4;
 314                                }
 315
 316                                errors += err_vec[i].error_count;
 317                        } else {
 318                                err_vec[i].error_uncorrectable = true;
 319                        }
 320
 321                        /* Clearing interrupts for processed error vectors */
 322                        elm_write_reg(info, ELM_IRQSTATUS, BIT(i));
 323
 324                        /* Disable page mode */
 325                        elm_configure_page_mode(info, i, false);
 326                }
 327        }
 328}
 329
 330/**
 331 * elm_decode_bch_error_page - Locate error position
 332 * @dev:        device pointer
 333 * @ecc_calc:   calculated ECC bytes from GPMC
 334 * @err_vec:    elm error vectors
 335 *
 336 * Called with one or more error reported vectors & vectors with
 337 * error reported is updated in err_vec[].error_reported
 338 */
 339void elm_decode_bch_error_page(struct device *dev, u8 *ecc_calc,
 340                struct elm_errorvec *err_vec)
 341{
 342        struct elm_info *info = dev_get_drvdata(dev);
 343        u32 reg_val;
 344
 345        /* Enable page mode interrupt */
 346        reg_val = elm_read_reg(info, ELM_IRQSTATUS);
 347        elm_write_reg(info, ELM_IRQSTATUS, reg_val & INTR_STATUS_PAGE_VALID);
 348        elm_write_reg(info, ELM_IRQENABLE, INTR_EN_PAGE_MASK);
 349
 350        /* Load valid ecc byte to syndrome fragment register */
 351        elm_load_syndrome(info, err_vec, ecc_calc);
 352
 353        /* Enable syndrome processing for which syndrome fragment is updated */
 354        elm_start_processing(info, err_vec);
 355
 356        /* Wait for ELM module to finish locating error correction */
 357        wait_for_completion(&info->elm_completion);
 358
 359        /* Disable page mode interrupt */
 360        reg_val = elm_read_reg(info, ELM_IRQENABLE);
 361        elm_write_reg(info, ELM_IRQENABLE, reg_val & ~INTR_EN_PAGE_MASK);
 362        elm_error_correction(info, err_vec);
 363}
 364EXPORT_SYMBOL(elm_decode_bch_error_page);
 365
 366static irqreturn_t elm_isr(int this_irq, void *dev_id)
 367{
 368        u32 reg_val;
 369        struct elm_info *info = dev_id;
 370
 371        reg_val = elm_read_reg(info, ELM_IRQSTATUS);
 372
 373        /* All error vectors processed */
 374        if (reg_val & INTR_STATUS_PAGE_VALID) {
 375                elm_write_reg(info, ELM_IRQSTATUS,
 376                                reg_val & INTR_STATUS_PAGE_VALID);
 377                complete(&info->elm_completion);
 378                return IRQ_HANDLED;
 379        }
 380
 381        return IRQ_NONE;
 382}
 383
 384static int elm_probe(struct platform_device *pdev)
 385{
 386        int ret = 0;
 387        struct resource *res, *irq;
 388        struct elm_info *info;
 389
 390        info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
 391        if (!info)
 392                return -ENOMEM;
 393
 394        info->dev = &pdev->dev;
 395
 396        irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
 397        if (!irq) {
 398                dev_err(&pdev->dev, "no irq resource defined\n");
 399                return -ENODEV;
 400        }
 401
 402        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 403        info->elm_base = devm_ioremap_resource(&pdev->dev, res);
 404        if (IS_ERR(info->elm_base))
 405                return PTR_ERR(info->elm_base);
 406
 407        ret = devm_request_irq(&pdev->dev, irq->start, elm_isr, 0,
 408                        pdev->name, info);
 409        if (ret) {
 410                dev_err(&pdev->dev, "failure requesting %pr\n", irq);
 411                return ret;
 412        }
 413
 414        pm_runtime_enable(&pdev->dev);
 415        if (pm_runtime_get_sync(&pdev->dev) < 0) {
 416                ret = -EINVAL;
 417                pm_runtime_put_sync(&pdev->dev);
 418                pm_runtime_disable(&pdev->dev);
 419                dev_err(&pdev->dev, "can't enable clock\n");
 420                return ret;
 421        }
 422
 423        init_completion(&info->elm_completion);
 424        INIT_LIST_HEAD(&info->list);
 425        list_add(&info->list, &elm_devices);
 426        platform_set_drvdata(pdev, info);
 427        return ret;
 428}
 429
 430static int elm_remove(struct platform_device *pdev)
 431{
 432        pm_runtime_put_sync(&pdev->dev);
 433        pm_runtime_disable(&pdev->dev);
 434        return 0;
 435}
 436
 437#ifdef CONFIG_PM_SLEEP
 438/*
 439 * elm_context_save
 440 * saves ELM configurations to preserve them across Hardware powered-down
 441 */
 442static int elm_context_save(struct elm_info *info)
 443{
 444        struct elm_registers *regs = &info->elm_regs;
 445        enum bch_ecc bch_type = info->bch_type;
 446        u32 offset = 0, i;
 447
 448        regs->elm_irqenable       = elm_read_reg(info, ELM_IRQENABLE);
 449        regs->elm_sysconfig       = elm_read_reg(info, ELM_SYSCONFIG);
 450        regs->elm_location_config = elm_read_reg(info, ELM_LOCATION_CONFIG);
 451        regs->elm_page_ctrl       = elm_read_reg(info, ELM_PAGE_CTRL);
 452        for (i = 0; i < ERROR_VECTOR_MAX; i++) {
 453                offset = i * SYNDROME_FRAGMENT_REG_SIZE;
 454                switch (bch_type) {
 455                case BCH16_ECC:
 456                        regs->elm_syndrome_fragment_6[i] = elm_read_reg(info,
 457                                        ELM_SYNDROME_FRAGMENT_6 + offset);
 458                        regs->elm_syndrome_fragment_5[i] = elm_read_reg(info,
 459                                        ELM_SYNDROME_FRAGMENT_5 + offset);
 460                        regs->elm_syndrome_fragment_4[i] = elm_read_reg(info,
 461                                        ELM_SYNDROME_FRAGMENT_4 + offset);
 462                        fallthrough;
 463                case BCH8_ECC:
 464                        regs->elm_syndrome_fragment_3[i] = elm_read_reg(info,
 465                                        ELM_SYNDROME_FRAGMENT_3 + offset);
 466                        regs->elm_syndrome_fragment_2[i] = elm_read_reg(info,
 467                                        ELM_SYNDROME_FRAGMENT_2 + offset);
 468                        fallthrough;
 469                case BCH4_ECC:
 470                        regs->elm_syndrome_fragment_1[i] = elm_read_reg(info,
 471                                        ELM_SYNDROME_FRAGMENT_1 + offset);
 472                        regs->elm_syndrome_fragment_0[i] = elm_read_reg(info,
 473                                        ELM_SYNDROME_FRAGMENT_0 + offset);
 474                        break;
 475                default:
 476                        return -EINVAL;
 477                }
 478                /* ELM SYNDROME_VALID bit in SYNDROME_FRAGMENT_6[] needs
 479                 * to be saved for all BCH schemes*/
 480                regs->elm_syndrome_fragment_6[i] = elm_read_reg(info,
 481                                        ELM_SYNDROME_FRAGMENT_6 + offset);
 482        }
 483        return 0;
 484}
 485
 486/*
 487 * elm_context_restore
 488 * writes configurations saved duing power-down back into ELM registers
 489 */
 490static int elm_context_restore(struct elm_info *info)
 491{
 492        struct elm_registers *regs = &info->elm_regs;
 493        enum bch_ecc bch_type = info->bch_type;
 494        u32 offset = 0, i;
 495
 496        elm_write_reg(info, ELM_IRQENABLE,       regs->elm_irqenable);
 497        elm_write_reg(info, ELM_SYSCONFIG,       regs->elm_sysconfig);
 498        elm_write_reg(info, ELM_LOCATION_CONFIG, regs->elm_location_config);
 499        elm_write_reg(info, ELM_PAGE_CTRL,       regs->elm_page_ctrl);
 500        for (i = 0; i < ERROR_VECTOR_MAX; i++) {
 501                offset = i * SYNDROME_FRAGMENT_REG_SIZE;
 502                switch (bch_type) {
 503                case BCH16_ECC:
 504                        elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset,
 505                                        regs->elm_syndrome_fragment_6[i]);
 506                        elm_write_reg(info, ELM_SYNDROME_FRAGMENT_5 + offset,
 507                                        regs->elm_syndrome_fragment_5[i]);
 508                        elm_write_reg(info, ELM_SYNDROME_FRAGMENT_4 + offset,
 509                                        regs->elm_syndrome_fragment_4[i]);
 510                        fallthrough;
 511                case BCH8_ECC:
 512                        elm_write_reg(info, ELM_SYNDROME_FRAGMENT_3 + offset,
 513                                        regs->elm_syndrome_fragment_3[i]);
 514                        elm_write_reg(info, ELM_SYNDROME_FRAGMENT_2 + offset,
 515                                        regs->elm_syndrome_fragment_2[i]);
 516                        fallthrough;
 517                case BCH4_ECC:
 518                        elm_write_reg(info, ELM_SYNDROME_FRAGMENT_1 + offset,
 519                                        regs->elm_syndrome_fragment_1[i]);
 520                        elm_write_reg(info, ELM_SYNDROME_FRAGMENT_0 + offset,
 521                                        regs->elm_syndrome_fragment_0[i]);
 522                        break;
 523                default:
 524                        return -EINVAL;
 525                }
 526                /* ELM_SYNDROME_VALID bit to be set in last to trigger FSM */
 527                elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset,
 528                                        regs->elm_syndrome_fragment_6[i] &
 529                                                         ELM_SYNDROME_VALID);
 530        }
 531        return 0;
 532}
 533
 534static int elm_suspend(struct device *dev)
 535{
 536        struct elm_info *info = dev_get_drvdata(dev);
 537        elm_context_save(info);
 538        pm_runtime_put_sync(dev);
 539        return 0;
 540}
 541
 542static int elm_resume(struct device *dev)
 543{
 544        struct elm_info *info = dev_get_drvdata(dev);
 545        pm_runtime_get_sync(dev);
 546        elm_context_restore(info);
 547        return 0;
 548}
 549#endif
 550
 551static SIMPLE_DEV_PM_OPS(elm_pm_ops, elm_suspend, elm_resume);
 552
 553#ifdef CONFIG_OF
 554static const struct of_device_id elm_of_match[] = {
 555        { .compatible = "ti,am3352-elm" },
 556        {},
 557};
 558MODULE_DEVICE_TABLE(of, elm_of_match);
 559#endif
 560
 561static struct platform_driver elm_driver = {
 562        .driver = {
 563                .name   = DRIVER_NAME,
 564                .of_match_table = of_match_ptr(elm_of_match),
 565                .pm     = &elm_pm_ops,
 566        },
 567        .probe  = elm_probe,
 568        .remove = elm_remove,
 569};
 570
 571module_platform_driver(elm_driver);
 572
 573MODULE_DESCRIPTION("ELM driver for BCH error correction");
 574MODULE_AUTHOR("Texas Instruments");
 575MODULE_ALIAS("platform:" DRIVER_NAME);
 576MODULE_LICENSE("GPL v2");
 577