572 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Error Location Module4 *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 0x01020#define ELM_IRQSTATUS 0x01821#define ELM_IRQENABLE 0x01c22#define ELM_LOCATION_CONFIG 0x02023#define ELM_PAGE_CTRL 0x08024#define ELM_SYNDROME_FRAGMENT_0 0x40025#define ELM_SYNDROME_FRAGMENT_1 0x40426#define ELM_SYNDROME_FRAGMENT_2 0x40827#define ELM_SYNDROME_FRAGMENT_3 0x40c28#define ELM_SYNDROME_FRAGMENT_4 0x41029#define ELM_SYNDROME_FRAGMENT_5 0x41430#define ELM_SYNDROME_FRAGMENT_6 0x41831#define ELM_LOCATION_STATUS 0x80032#define ELM_ERROR_LOCATION_0 0x88033 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 0x342 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 0x1f49 50/* ELM_ERROR_LOCATION_0-15 Registers */51#define ECC_ERROR_LOCATION_MASK 0x1fff52 53#define ELM_ECC_SIZE 0x7ff54 55#define SYNDROME_FRAGMENT_REG_SIZE 0x4056#define ERROR_LOCATION_SIZE 0x10057 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 module97 * @dev: ELM device98 * @bch_type: Type of BCH ecc99 * @ecc_steps: ECC steps to assign to config100 * @ecc_step_size: ECC step size to assign to config101 * @ecc_syndrome_size: ECC syndrome size to assign to config102 */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 mode136 * @info: elm info137 * @index: index number of syndrome fragment vector138 * @enable: enable/disable flag for page mode139 *140 * Enable page mode for syndrome fragment index141 */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 else151 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 reg158 * @info: elm info159 * @err_vec: elm error vectors160 * @ecc: buffer with calculated ecc161 *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 = (__force u32)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 = (__force u32)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 = (__force u32)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 = ((__force u32)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 = (__force u32)cpu_to_be32(*(u32 *)&ecc[0]) >> 12;207 elm_write_reg(info, offset, val);208 break;209 case BCH16_ECC:210 val = (__force u32)cpu_to_be32(*(u32 *)&ecc[22]);211 elm_write_reg(info, offset, val);212 offset += 4;213 val = (__force u32)cpu_to_be32(*(u32 *)&ecc[18]);214 elm_write_reg(info, offset, val);215 offset += 4;216 val = (__force u32)cpu_to_be32(*(u32 *)&ecc[14]);217 elm_write_reg(info, offset, val);218 offset += 4;219 val = (__force u32)cpu_to_be32(*(u32 *)&ecc[10]);220 elm_write_reg(info, offset, val);221 offset += 4;222 val = (__force u32)cpu_to_be32(*(u32 *)&ecc[6]);223 elm_write_reg(info, offset, val);224 offset += 4;225 val = (__force u32)cpu_to_be32(*(u32 *)&ecc[2]);226 elm_write_reg(info, offset, val);227 offset += 4;228 val = (__force u32)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 processing243 * @info: elm info244 * @err_vec: elm error vectors245 *246 * Set syndrome valid bit for syndrome fragment registers for which247 * elm syndrome fragment registers are loaded. This enables elm module248 * 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 module258 * will process it for vectors error is reported259 */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 position273 * @info: elm info274 * @err_vec: elm error vectors275 *276 * On completion of processing by elm module, error location status277 * register updated with correctable/uncorrectable error information.278 * In case of correctable errors, number of errors located from279 * elm location status register & read the positions from280 * 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;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 } else {316 err_vec[i].error_uncorrectable = true;317 }318 319 /* Clearing interrupts for processed error vectors */320 elm_write_reg(info, ELM_IRQSTATUS, BIT(i));321 322 /* Disable page mode */323 elm_configure_page_mode(info, i, false);324 }325 }326}327 328/**329 * elm_decode_bch_error_page - Locate error position330 * @dev: device pointer331 * @ecc_calc: calculated ECC bytes from GPMC332 * @err_vec: elm error vectors333 *334 * Called with one or more error reported vectors & vectors with335 * error reported is updated in err_vec[].error_reported336 */337void elm_decode_bch_error_page(struct device *dev, u8 *ecc_calc,338 struct elm_errorvec *err_vec)339{340 struct elm_info *info = dev_get_drvdata(dev);341 u32 reg_val;342 343 /* Enable page mode interrupt */344 reg_val = elm_read_reg(info, ELM_IRQSTATUS);345 elm_write_reg(info, ELM_IRQSTATUS, reg_val & INTR_STATUS_PAGE_VALID);346 elm_write_reg(info, ELM_IRQENABLE, INTR_EN_PAGE_MASK);347 348 /* Load valid ecc byte to syndrome fragment register */349 elm_load_syndrome(info, err_vec, ecc_calc);350 351 /* Enable syndrome processing for which syndrome fragment is updated */352 elm_start_processing(info, err_vec);353 354 /* Wait for ELM module to finish locating error correction */355 wait_for_completion(&info->elm_completion);356 357 /* Disable page mode interrupt */358 reg_val = elm_read_reg(info, ELM_IRQENABLE);359 elm_write_reg(info, ELM_IRQENABLE, reg_val & ~INTR_EN_PAGE_MASK);360 elm_error_correction(info, err_vec);361}362EXPORT_SYMBOL(elm_decode_bch_error_page);363 364static irqreturn_t elm_isr(int this_irq, void *dev_id)365{366 u32 reg_val;367 struct elm_info *info = dev_id;368 369 reg_val = elm_read_reg(info, ELM_IRQSTATUS);370 371 /* All error vectors processed */372 if (reg_val & INTR_STATUS_PAGE_VALID) {373 elm_write_reg(info, ELM_IRQSTATUS,374 reg_val & INTR_STATUS_PAGE_VALID);375 complete(&info->elm_completion);376 return IRQ_HANDLED;377 }378 379 return IRQ_NONE;380}381 382static int elm_probe(struct platform_device *pdev)383{384 int ret = 0;385 struct elm_info *info;386 int irq;387 388 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);389 if (!info)390 return -ENOMEM;391 392 info->dev = &pdev->dev;393 394 irq = platform_get_irq(pdev, 0);395 if (irq < 0)396 return irq;397 398 info->elm_base = devm_platform_ioremap_resource(pdev, 0);399 if (IS_ERR(info->elm_base))400 return PTR_ERR(info->elm_base);401 402 ret = devm_request_irq(&pdev->dev, irq, elm_isr, 0,403 pdev->name, info);404 if (ret) {405 dev_err(&pdev->dev, "failure requesting %d\n", irq);406 return ret;407 }408 409 pm_runtime_enable(&pdev->dev);410 if (pm_runtime_get_sync(&pdev->dev) < 0) {411 ret = -EINVAL;412 pm_runtime_put_sync(&pdev->dev);413 pm_runtime_disable(&pdev->dev);414 dev_err(&pdev->dev, "can't enable clock\n");415 return ret;416 }417 418 init_completion(&info->elm_completion);419 INIT_LIST_HEAD(&info->list);420 list_add(&info->list, &elm_devices);421 platform_set_drvdata(pdev, info);422 return ret;423}424 425static void elm_remove(struct platform_device *pdev)426{427 pm_runtime_put_sync(&pdev->dev);428 pm_runtime_disable(&pdev->dev);429}430 431#ifdef CONFIG_PM_SLEEP432/*433 * elm_context_save434 * saves ELM configurations to preserve them across Hardware powered-down435 */436static int elm_context_save(struct elm_info *info)437{438 struct elm_registers *regs = &info->elm_regs;439 enum bch_ecc bch_type = info->bch_type;440 u32 offset = 0, i;441 442 regs->elm_irqenable = elm_read_reg(info, ELM_IRQENABLE);443 regs->elm_sysconfig = elm_read_reg(info, ELM_SYSCONFIG);444 regs->elm_location_config = elm_read_reg(info, ELM_LOCATION_CONFIG);445 regs->elm_page_ctrl = elm_read_reg(info, ELM_PAGE_CTRL);446 for (i = 0; i < ERROR_VECTOR_MAX; i++) {447 offset = i * SYNDROME_FRAGMENT_REG_SIZE;448 switch (bch_type) {449 case BCH16_ECC:450 regs->elm_syndrome_fragment_6[i] = elm_read_reg(info,451 ELM_SYNDROME_FRAGMENT_6 + offset);452 regs->elm_syndrome_fragment_5[i] = elm_read_reg(info,453 ELM_SYNDROME_FRAGMENT_5 + offset);454 regs->elm_syndrome_fragment_4[i] = elm_read_reg(info,455 ELM_SYNDROME_FRAGMENT_4 + offset);456 fallthrough;457 case BCH8_ECC:458 regs->elm_syndrome_fragment_3[i] = elm_read_reg(info,459 ELM_SYNDROME_FRAGMENT_3 + offset);460 regs->elm_syndrome_fragment_2[i] = elm_read_reg(info,461 ELM_SYNDROME_FRAGMENT_2 + offset);462 fallthrough;463 case BCH4_ECC:464 regs->elm_syndrome_fragment_1[i] = elm_read_reg(info,465 ELM_SYNDROME_FRAGMENT_1 + offset);466 regs->elm_syndrome_fragment_0[i] = elm_read_reg(info,467 ELM_SYNDROME_FRAGMENT_0 + offset);468 break;469 default:470 return -EINVAL;471 }472 /* ELM SYNDROME_VALID bit in SYNDROME_FRAGMENT_6[] needs473 * to be saved for all BCH schemes*/474 regs->elm_syndrome_fragment_6[i] = elm_read_reg(info,475 ELM_SYNDROME_FRAGMENT_6 + offset);476 }477 return 0;478}479 480/*481 * elm_context_restore482 * writes configurations saved duing power-down back into ELM registers483 */484static int elm_context_restore(struct elm_info *info)485{486 struct elm_registers *regs = &info->elm_regs;487 enum bch_ecc bch_type = info->bch_type;488 u32 offset = 0, i;489 490 elm_write_reg(info, ELM_IRQENABLE, regs->elm_irqenable);491 elm_write_reg(info, ELM_SYSCONFIG, regs->elm_sysconfig);492 elm_write_reg(info, ELM_LOCATION_CONFIG, regs->elm_location_config);493 elm_write_reg(info, ELM_PAGE_CTRL, regs->elm_page_ctrl);494 for (i = 0; i < ERROR_VECTOR_MAX; i++) {495 offset = i * SYNDROME_FRAGMENT_REG_SIZE;496 switch (bch_type) {497 case BCH16_ECC:498 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset,499 regs->elm_syndrome_fragment_6[i]);500 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_5 + offset,501 regs->elm_syndrome_fragment_5[i]);502 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_4 + offset,503 regs->elm_syndrome_fragment_4[i]);504 fallthrough;505 case BCH8_ECC:506 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_3 + offset,507 regs->elm_syndrome_fragment_3[i]);508 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_2 + offset,509 regs->elm_syndrome_fragment_2[i]);510 fallthrough;511 case BCH4_ECC:512 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_1 + offset,513 regs->elm_syndrome_fragment_1[i]);514 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_0 + offset,515 regs->elm_syndrome_fragment_0[i]);516 break;517 default:518 return -EINVAL;519 }520 /* ELM_SYNDROME_VALID bit to be set in last to trigger FSM */521 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset,522 regs->elm_syndrome_fragment_6[i] &523 ELM_SYNDROME_VALID);524 }525 return 0;526}527 528static int elm_suspend(struct device *dev)529{530 struct elm_info *info = dev_get_drvdata(dev);531 elm_context_save(info);532 pm_runtime_put_sync(dev);533 return 0;534}535 536static int elm_resume(struct device *dev)537{538 struct elm_info *info = dev_get_drvdata(dev);539 pm_runtime_get_sync(dev);540 elm_context_restore(info);541 return 0;542}543#endif544 545static SIMPLE_DEV_PM_OPS(elm_pm_ops, elm_suspend, elm_resume);546 547#ifdef CONFIG_OF548static const struct of_device_id elm_of_match[] = {549 { .compatible = "ti,am3352-elm" },550 { .compatible = "ti,am64-elm" },551 {},552};553MODULE_DEVICE_TABLE(of, elm_of_match);554#endif555 556static struct platform_driver elm_driver = {557 .driver = {558 .name = DRIVER_NAME,559 .of_match_table = of_match_ptr(elm_of_match),560 .pm = &elm_pm_ops,561 },562 .probe = elm_probe,563 .remove_new = elm_remove,564};565 566module_platform_driver(elm_driver);567 568MODULE_DESCRIPTION("ELM driver for BCH error correction");569MODULE_AUTHOR("Texas Instruments");570MODULE_ALIAS("platform:" DRIVER_NAME);571MODULE_LICENSE("GPL v2");572