162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Error Location Module 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com/ 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#define DRIVER_NAME "omap-elm" 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/platform_device.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/interrupt.h> 1362306a36Sopenharmony_ci#include <linux/io.h> 1462306a36Sopenharmony_ci#include <linux/of.h> 1562306a36Sopenharmony_ci#include <linux/sched.h> 1662306a36Sopenharmony_ci#include <linux/pm_runtime.h> 1762306a36Sopenharmony_ci#include <linux/platform_data/elm.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#define ELM_SYSCONFIG 0x010 2062306a36Sopenharmony_ci#define ELM_IRQSTATUS 0x018 2162306a36Sopenharmony_ci#define ELM_IRQENABLE 0x01c 2262306a36Sopenharmony_ci#define ELM_LOCATION_CONFIG 0x020 2362306a36Sopenharmony_ci#define ELM_PAGE_CTRL 0x080 2462306a36Sopenharmony_ci#define ELM_SYNDROME_FRAGMENT_0 0x400 2562306a36Sopenharmony_ci#define ELM_SYNDROME_FRAGMENT_1 0x404 2662306a36Sopenharmony_ci#define ELM_SYNDROME_FRAGMENT_2 0x408 2762306a36Sopenharmony_ci#define ELM_SYNDROME_FRAGMENT_3 0x40c 2862306a36Sopenharmony_ci#define ELM_SYNDROME_FRAGMENT_4 0x410 2962306a36Sopenharmony_ci#define ELM_SYNDROME_FRAGMENT_5 0x414 3062306a36Sopenharmony_ci#define ELM_SYNDROME_FRAGMENT_6 0x418 3162306a36Sopenharmony_ci#define ELM_LOCATION_STATUS 0x800 3262306a36Sopenharmony_ci#define ELM_ERROR_LOCATION_0 0x880 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci/* ELM Interrupt Status Register */ 3562306a36Sopenharmony_ci#define INTR_STATUS_PAGE_VALID BIT(8) 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci/* ELM Interrupt Enable Register */ 3862306a36Sopenharmony_ci#define INTR_EN_PAGE_MASK BIT(8) 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci/* ELM Location Configuration Register */ 4162306a36Sopenharmony_ci#define ECC_BCH_LEVEL_MASK 0x3 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci/* ELM syndrome */ 4462306a36Sopenharmony_ci#define ELM_SYNDROME_VALID BIT(16) 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci/* ELM_LOCATION_STATUS Register */ 4762306a36Sopenharmony_ci#define ECC_CORRECTABLE_MASK BIT(8) 4862306a36Sopenharmony_ci#define ECC_NB_ERRORS_MASK 0x1f 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci/* ELM_ERROR_LOCATION_0-15 Registers */ 5162306a36Sopenharmony_ci#define ECC_ERROR_LOCATION_MASK 0x1fff 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci#define ELM_ECC_SIZE 0x7ff 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci#define SYNDROME_FRAGMENT_REG_SIZE 0x40 5662306a36Sopenharmony_ci#define ERROR_LOCATION_SIZE 0x100 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistruct elm_registers { 5962306a36Sopenharmony_ci u32 elm_irqenable; 6062306a36Sopenharmony_ci u32 elm_sysconfig; 6162306a36Sopenharmony_ci u32 elm_location_config; 6262306a36Sopenharmony_ci u32 elm_page_ctrl; 6362306a36Sopenharmony_ci u32 elm_syndrome_fragment_6[ERROR_VECTOR_MAX]; 6462306a36Sopenharmony_ci u32 elm_syndrome_fragment_5[ERROR_VECTOR_MAX]; 6562306a36Sopenharmony_ci u32 elm_syndrome_fragment_4[ERROR_VECTOR_MAX]; 6662306a36Sopenharmony_ci u32 elm_syndrome_fragment_3[ERROR_VECTOR_MAX]; 6762306a36Sopenharmony_ci u32 elm_syndrome_fragment_2[ERROR_VECTOR_MAX]; 6862306a36Sopenharmony_ci u32 elm_syndrome_fragment_1[ERROR_VECTOR_MAX]; 6962306a36Sopenharmony_ci u32 elm_syndrome_fragment_0[ERROR_VECTOR_MAX]; 7062306a36Sopenharmony_ci}; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistruct elm_info { 7362306a36Sopenharmony_ci struct device *dev; 7462306a36Sopenharmony_ci void __iomem *elm_base; 7562306a36Sopenharmony_ci struct completion elm_completion; 7662306a36Sopenharmony_ci struct list_head list; 7762306a36Sopenharmony_ci enum bch_ecc bch_type; 7862306a36Sopenharmony_ci struct elm_registers elm_regs; 7962306a36Sopenharmony_ci int ecc_steps; 8062306a36Sopenharmony_ci int ecc_syndrome_size; 8162306a36Sopenharmony_ci}; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic LIST_HEAD(elm_devices); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic void elm_write_reg(struct elm_info *info, int offset, u32 val) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci writel(val, info->elm_base + offset); 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistatic u32 elm_read_reg(struct elm_info *info, int offset) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci return readl(info->elm_base + offset); 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci/** 9662306a36Sopenharmony_ci * elm_config - Configure ELM module 9762306a36Sopenharmony_ci * @dev: ELM device 9862306a36Sopenharmony_ci * @bch_type: Type of BCH ecc 9962306a36Sopenharmony_ci * @ecc_steps: ECC steps to assign to config 10062306a36Sopenharmony_ci * @ecc_step_size: ECC step size to assign to config 10162306a36Sopenharmony_ci * @ecc_syndrome_size: ECC syndrome size to assign to config 10262306a36Sopenharmony_ci */ 10362306a36Sopenharmony_ciint elm_config(struct device *dev, enum bch_ecc bch_type, 10462306a36Sopenharmony_ci int ecc_steps, int ecc_step_size, int ecc_syndrome_size) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci u32 reg_val; 10762306a36Sopenharmony_ci struct elm_info *info = dev_get_drvdata(dev); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci if (!info) { 11062306a36Sopenharmony_ci dev_err(dev, "Unable to configure elm - device not probed?\n"); 11162306a36Sopenharmony_ci return -EPROBE_DEFER; 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ci /* ELM cannot detect ECC errors for chunks > 1KB */ 11462306a36Sopenharmony_ci if (ecc_step_size > ((ELM_ECC_SIZE + 1) / 2)) { 11562306a36Sopenharmony_ci dev_err(dev, "unsupported config ecc-size=%d\n", ecc_step_size); 11662306a36Sopenharmony_ci return -EINVAL; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci /* ELM support 8 error syndrome process */ 11962306a36Sopenharmony_ci if (ecc_steps > ERROR_VECTOR_MAX && ecc_steps % ERROR_VECTOR_MAX) { 12062306a36Sopenharmony_ci dev_err(dev, "unsupported config ecc-step=%d\n", ecc_steps); 12162306a36Sopenharmony_ci return -EINVAL; 12262306a36Sopenharmony_ci } 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci reg_val = (bch_type & ECC_BCH_LEVEL_MASK) | (ELM_ECC_SIZE << 16); 12562306a36Sopenharmony_ci elm_write_reg(info, ELM_LOCATION_CONFIG, reg_val); 12662306a36Sopenharmony_ci info->bch_type = bch_type; 12762306a36Sopenharmony_ci info->ecc_steps = ecc_steps; 12862306a36Sopenharmony_ci info->ecc_syndrome_size = ecc_syndrome_size; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci return 0; 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ciEXPORT_SYMBOL(elm_config); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci/** 13562306a36Sopenharmony_ci * elm_configure_page_mode - Enable/Disable page mode 13662306a36Sopenharmony_ci * @info: elm info 13762306a36Sopenharmony_ci * @index: index number of syndrome fragment vector 13862306a36Sopenharmony_ci * @enable: enable/disable flag for page mode 13962306a36Sopenharmony_ci * 14062306a36Sopenharmony_ci * Enable page mode for syndrome fragment index 14162306a36Sopenharmony_ci */ 14262306a36Sopenharmony_cistatic void elm_configure_page_mode(struct elm_info *info, int index, 14362306a36Sopenharmony_ci bool enable) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci u32 reg_val; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci reg_val = elm_read_reg(info, ELM_PAGE_CTRL); 14862306a36Sopenharmony_ci if (enable) 14962306a36Sopenharmony_ci reg_val |= BIT(index); /* enable page mode */ 15062306a36Sopenharmony_ci else 15162306a36Sopenharmony_ci reg_val &= ~BIT(index); /* disable page mode */ 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci elm_write_reg(info, ELM_PAGE_CTRL, reg_val); 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci/** 15762306a36Sopenharmony_ci * elm_load_syndrome - Load ELM syndrome reg 15862306a36Sopenharmony_ci * @info: elm info 15962306a36Sopenharmony_ci * @err_vec: elm error vectors 16062306a36Sopenharmony_ci * @ecc: buffer with calculated ecc 16162306a36Sopenharmony_ci * 16262306a36Sopenharmony_ci * Load syndrome fragment registers with calculated ecc in reverse order. 16362306a36Sopenharmony_ci */ 16462306a36Sopenharmony_cistatic void elm_load_syndrome(struct elm_info *info, 16562306a36Sopenharmony_ci struct elm_errorvec *err_vec, u8 *ecc) 16662306a36Sopenharmony_ci{ 16762306a36Sopenharmony_ci int i, offset; 16862306a36Sopenharmony_ci u32 val; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci for (i = 0; i < info->ecc_steps; i++) { 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci /* Check error reported */ 17362306a36Sopenharmony_ci if (err_vec[i].error_reported) { 17462306a36Sopenharmony_ci elm_configure_page_mode(info, i, true); 17562306a36Sopenharmony_ci offset = ELM_SYNDROME_FRAGMENT_0 + 17662306a36Sopenharmony_ci SYNDROME_FRAGMENT_REG_SIZE * i; 17762306a36Sopenharmony_ci switch (info->bch_type) { 17862306a36Sopenharmony_ci case BCH8_ECC: 17962306a36Sopenharmony_ci /* syndrome fragment 0 = ecc[9-12B] */ 18062306a36Sopenharmony_ci val = (__force u32)cpu_to_be32(*(u32 *)&ecc[9]); 18162306a36Sopenharmony_ci elm_write_reg(info, offset, val); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci /* syndrome fragment 1 = ecc[5-8B] */ 18462306a36Sopenharmony_ci offset += 4; 18562306a36Sopenharmony_ci val = (__force u32)cpu_to_be32(*(u32 *)&ecc[5]); 18662306a36Sopenharmony_ci elm_write_reg(info, offset, val); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci /* syndrome fragment 2 = ecc[1-4B] */ 18962306a36Sopenharmony_ci offset += 4; 19062306a36Sopenharmony_ci val = (__force u32)cpu_to_be32(*(u32 *)&ecc[1]); 19162306a36Sopenharmony_ci elm_write_reg(info, offset, val); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci /* syndrome fragment 3 = ecc[0B] */ 19462306a36Sopenharmony_ci offset += 4; 19562306a36Sopenharmony_ci val = ecc[0]; 19662306a36Sopenharmony_ci elm_write_reg(info, offset, val); 19762306a36Sopenharmony_ci break; 19862306a36Sopenharmony_ci case BCH4_ECC: 19962306a36Sopenharmony_ci /* syndrome fragment 0 = ecc[20-52b] bits */ 20062306a36Sopenharmony_ci val = ((__force u32)cpu_to_be32(*(u32 *)&ecc[3]) >> 4) | 20162306a36Sopenharmony_ci ((ecc[2] & 0xf) << 28); 20262306a36Sopenharmony_ci elm_write_reg(info, offset, val); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci /* syndrome fragment 1 = ecc[0-20b] bits */ 20562306a36Sopenharmony_ci offset += 4; 20662306a36Sopenharmony_ci val = (__force u32)cpu_to_be32(*(u32 *)&ecc[0]) >> 12; 20762306a36Sopenharmony_ci elm_write_reg(info, offset, val); 20862306a36Sopenharmony_ci break; 20962306a36Sopenharmony_ci case BCH16_ECC: 21062306a36Sopenharmony_ci val = (__force u32)cpu_to_be32(*(u32 *)&ecc[22]); 21162306a36Sopenharmony_ci elm_write_reg(info, offset, val); 21262306a36Sopenharmony_ci offset += 4; 21362306a36Sopenharmony_ci val = (__force u32)cpu_to_be32(*(u32 *)&ecc[18]); 21462306a36Sopenharmony_ci elm_write_reg(info, offset, val); 21562306a36Sopenharmony_ci offset += 4; 21662306a36Sopenharmony_ci val = (__force u32)cpu_to_be32(*(u32 *)&ecc[14]); 21762306a36Sopenharmony_ci elm_write_reg(info, offset, val); 21862306a36Sopenharmony_ci offset += 4; 21962306a36Sopenharmony_ci val = (__force u32)cpu_to_be32(*(u32 *)&ecc[10]); 22062306a36Sopenharmony_ci elm_write_reg(info, offset, val); 22162306a36Sopenharmony_ci offset += 4; 22262306a36Sopenharmony_ci val = (__force u32)cpu_to_be32(*(u32 *)&ecc[6]); 22362306a36Sopenharmony_ci elm_write_reg(info, offset, val); 22462306a36Sopenharmony_ci offset += 4; 22562306a36Sopenharmony_ci val = (__force u32)cpu_to_be32(*(u32 *)&ecc[2]); 22662306a36Sopenharmony_ci elm_write_reg(info, offset, val); 22762306a36Sopenharmony_ci offset += 4; 22862306a36Sopenharmony_ci val = (__force u32)cpu_to_be32(*(u32 *)&ecc[0]) >> 16; 22962306a36Sopenharmony_ci elm_write_reg(info, offset, val); 23062306a36Sopenharmony_ci break; 23162306a36Sopenharmony_ci default: 23262306a36Sopenharmony_ci pr_err("invalid config bch_type\n"); 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci /* Update ecc pointer with ecc byte size */ 23762306a36Sopenharmony_ci ecc += info->ecc_syndrome_size; 23862306a36Sopenharmony_ci } 23962306a36Sopenharmony_ci} 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci/** 24262306a36Sopenharmony_ci * elm_start_processing - start elm syndrome processing 24362306a36Sopenharmony_ci * @info: elm info 24462306a36Sopenharmony_ci * @err_vec: elm error vectors 24562306a36Sopenharmony_ci * 24662306a36Sopenharmony_ci * Set syndrome valid bit for syndrome fragment registers for which 24762306a36Sopenharmony_ci * elm syndrome fragment registers are loaded. This enables elm module 24862306a36Sopenharmony_ci * to start processing syndrome vectors. 24962306a36Sopenharmony_ci */ 25062306a36Sopenharmony_cistatic void elm_start_processing(struct elm_info *info, 25162306a36Sopenharmony_ci struct elm_errorvec *err_vec) 25262306a36Sopenharmony_ci{ 25362306a36Sopenharmony_ci int i, offset; 25462306a36Sopenharmony_ci u32 reg_val; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci /* 25762306a36Sopenharmony_ci * Set syndrome vector valid, so that ELM module 25862306a36Sopenharmony_ci * will process it for vectors error is reported 25962306a36Sopenharmony_ci */ 26062306a36Sopenharmony_ci for (i = 0; i < info->ecc_steps; i++) { 26162306a36Sopenharmony_ci if (err_vec[i].error_reported) { 26262306a36Sopenharmony_ci offset = ELM_SYNDROME_FRAGMENT_6 + 26362306a36Sopenharmony_ci SYNDROME_FRAGMENT_REG_SIZE * i; 26462306a36Sopenharmony_ci reg_val = elm_read_reg(info, offset); 26562306a36Sopenharmony_ci reg_val |= ELM_SYNDROME_VALID; 26662306a36Sopenharmony_ci elm_write_reg(info, offset, reg_val); 26762306a36Sopenharmony_ci } 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci} 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci/** 27262306a36Sopenharmony_ci * elm_error_correction - locate correctable error position 27362306a36Sopenharmony_ci * @info: elm info 27462306a36Sopenharmony_ci * @err_vec: elm error vectors 27562306a36Sopenharmony_ci * 27662306a36Sopenharmony_ci * On completion of processing by elm module, error location status 27762306a36Sopenharmony_ci * register updated with correctable/uncorrectable error information. 27862306a36Sopenharmony_ci * In case of correctable errors, number of errors located from 27962306a36Sopenharmony_ci * elm location status register & read the positions from 28062306a36Sopenharmony_ci * elm error location register. 28162306a36Sopenharmony_ci */ 28262306a36Sopenharmony_cistatic void elm_error_correction(struct elm_info *info, 28362306a36Sopenharmony_ci struct elm_errorvec *err_vec) 28462306a36Sopenharmony_ci{ 28562306a36Sopenharmony_ci int i, j; 28662306a36Sopenharmony_ci int offset; 28762306a36Sopenharmony_ci u32 reg_val; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci for (i = 0; i < info->ecc_steps; i++) { 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci /* Check error reported */ 29262306a36Sopenharmony_ci if (err_vec[i].error_reported) { 29362306a36Sopenharmony_ci offset = ELM_LOCATION_STATUS + ERROR_LOCATION_SIZE * i; 29462306a36Sopenharmony_ci reg_val = elm_read_reg(info, offset); 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci /* Check correctable error or not */ 29762306a36Sopenharmony_ci if (reg_val & ECC_CORRECTABLE_MASK) { 29862306a36Sopenharmony_ci offset = ELM_ERROR_LOCATION_0 + 29962306a36Sopenharmony_ci ERROR_LOCATION_SIZE * i; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci /* Read count of correctable errors */ 30262306a36Sopenharmony_ci err_vec[i].error_count = reg_val & 30362306a36Sopenharmony_ci ECC_NB_ERRORS_MASK; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci /* Update the error locations in error vector */ 30662306a36Sopenharmony_ci for (j = 0; j < err_vec[i].error_count; j++) { 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci reg_val = elm_read_reg(info, offset); 30962306a36Sopenharmony_ci err_vec[i].error_loc[j] = reg_val & 31062306a36Sopenharmony_ci ECC_ERROR_LOCATION_MASK; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* Update error location register */ 31362306a36Sopenharmony_ci offset += 4; 31462306a36Sopenharmony_ci } 31562306a36Sopenharmony_ci } else { 31662306a36Sopenharmony_ci err_vec[i].error_uncorrectable = true; 31762306a36Sopenharmony_ci } 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci /* Clearing interrupts for processed error vectors */ 32062306a36Sopenharmony_ci elm_write_reg(info, ELM_IRQSTATUS, BIT(i)); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci /* Disable page mode */ 32362306a36Sopenharmony_ci elm_configure_page_mode(info, i, false); 32462306a36Sopenharmony_ci } 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci} 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci/** 32962306a36Sopenharmony_ci * elm_decode_bch_error_page - Locate error position 33062306a36Sopenharmony_ci * @dev: device pointer 33162306a36Sopenharmony_ci * @ecc_calc: calculated ECC bytes from GPMC 33262306a36Sopenharmony_ci * @err_vec: elm error vectors 33362306a36Sopenharmony_ci * 33462306a36Sopenharmony_ci * Called with one or more error reported vectors & vectors with 33562306a36Sopenharmony_ci * error reported is updated in err_vec[].error_reported 33662306a36Sopenharmony_ci */ 33762306a36Sopenharmony_civoid elm_decode_bch_error_page(struct device *dev, u8 *ecc_calc, 33862306a36Sopenharmony_ci struct elm_errorvec *err_vec) 33962306a36Sopenharmony_ci{ 34062306a36Sopenharmony_ci struct elm_info *info = dev_get_drvdata(dev); 34162306a36Sopenharmony_ci u32 reg_val; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci /* Enable page mode interrupt */ 34462306a36Sopenharmony_ci reg_val = elm_read_reg(info, ELM_IRQSTATUS); 34562306a36Sopenharmony_ci elm_write_reg(info, ELM_IRQSTATUS, reg_val & INTR_STATUS_PAGE_VALID); 34662306a36Sopenharmony_ci elm_write_reg(info, ELM_IRQENABLE, INTR_EN_PAGE_MASK); 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci /* Load valid ecc byte to syndrome fragment register */ 34962306a36Sopenharmony_ci elm_load_syndrome(info, err_vec, ecc_calc); 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci /* Enable syndrome processing for which syndrome fragment is updated */ 35262306a36Sopenharmony_ci elm_start_processing(info, err_vec); 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci /* Wait for ELM module to finish locating error correction */ 35562306a36Sopenharmony_ci wait_for_completion(&info->elm_completion); 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci /* Disable page mode interrupt */ 35862306a36Sopenharmony_ci reg_val = elm_read_reg(info, ELM_IRQENABLE); 35962306a36Sopenharmony_ci elm_write_reg(info, ELM_IRQENABLE, reg_val & ~INTR_EN_PAGE_MASK); 36062306a36Sopenharmony_ci elm_error_correction(info, err_vec); 36162306a36Sopenharmony_ci} 36262306a36Sopenharmony_ciEXPORT_SYMBOL(elm_decode_bch_error_page); 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_cistatic irqreturn_t elm_isr(int this_irq, void *dev_id) 36562306a36Sopenharmony_ci{ 36662306a36Sopenharmony_ci u32 reg_val; 36762306a36Sopenharmony_ci struct elm_info *info = dev_id; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci reg_val = elm_read_reg(info, ELM_IRQSTATUS); 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci /* All error vectors processed */ 37262306a36Sopenharmony_ci if (reg_val & INTR_STATUS_PAGE_VALID) { 37362306a36Sopenharmony_ci elm_write_reg(info, ELM_IRQSTATUS, 37462306a36Sopenharmony_ci reg_val & INTR_STATUS_PAGE_VALID); 37562306a36Sopenharmony_ci complete(&info->elm_completion); 37662306a36Sopenharmony_ci return IRQ_HANDLED; 37762306a36Sopenharmony_ci } 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci return IRQ_NONE; 38062306a36Sopenharmony_ci} 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_cistatic int elm_probe(struct platform_device *pdev) 38362306a36Sopenharmony_ci{ 38462306a36Sopenharmony_ci int ret = 0; 38562306a36Sopenharmony_ci struct elm_info *info; 38662306a36Sopenharmony_ci int irq; 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); 38962306a36Sopenharmony_ci if (!info) 39062306a36Sopenharmony_ci return -ENOMEM; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci info->dev = &pdev->dev; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci irq = platform_get_irq(pdev, 0); 39562306a36Sopenharmony_ci if (irq < 0) 39662306a36Sopenharmony_ci return irq; 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci info->elm_base = devm_platform_ioremap_resource(pdev, 0); 39962306a36Sopenharmony_ci if (IS_ERR(info->elm_base)) 40062306a36Sopenharmony_ci return PTR_ERR(info->elm_base); 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci ret = devm_request_irq(&pdev->dev, irq, elm_isr, 0, 40362306a36Sopenharmony_ci pdev->name, info); 40462306a36Sopenharmony_ci if (ret) { 40562306a36Sopenharmony_ci dev_err(&pdev->dev, "failure requesting %d\n", irq); 40662306a36Sopenharmony_ci return ret; 40762306a36Sopenharmony_ci } 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci pm_runtime_enable(&pdev->dev); 41062306a36Sopenharmony_ci if (pm_runtime_get_sync(&pdev->dev) < 0) { 41162306a36Sopenharmony_ci ret = -EINVAL; 41262306a36Sopenharmony_ci pm_runtime_put_sync(&pdev->dev); 41362306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 41462306a36Sopenharmony_ci dev_err(&pdev->dev, "can't enable clock\n"); 41562306a36Sopenharmony_ci return ret; 41662306a36Sopenharmony_ci } 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci init_completion(&info->elm_completion); 41962306a36Sopenharmony_ci INIT_LIST_HEAD(&info->list); 42062306a36Sopenharmony_ci list_add(&info->list, &elm_devices); 42162306a36Sopenharmony_ci platform_set_drvdata(pdev, info); 42262306a36Sopenharmony_ci return ret; 42362306a36Sopenharmony_ci} 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_cistatic void elm_remove(struct platform_device *pdev) 42662306a36Sopenharmony_ci{ 42762306a36Sopenharmony_ci pm_runtime_put_sync(&pdev->dev); 42862306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 42962306a36Sopenharmony_ci} 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 43262306a36Sopenharmony_ci/* 43362306a36Sopenharmony_ci * elm_context_save 43462306a36Sopenharmony_ci * saves ELM configurations to preserve them across Hardware powered-down 43562306a36Sopenharmony_ci */ 43662306a36Sopenharmony_cistatic int elm_context_save(struct elm_info *info) 43762306a36Sopenharmony_ci{ 43862306a36Sopenharmony_ci struct elm_registers *regs = &info->elm_regs; 43962306a36Sopenharmony_ci enum bch_ecc bch_type = info->bch_type; 44062306a36Sopenharmony_ci u32 offset = 0, i; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci regs->elm_irqenable = elm_read_reg(info, ELM_IRQENABLE); 44362306a36Sopenharmony_ci regs->elm_sysconfig = elm_read_reg(info, ELM_SYSCONFIG); 44462306a36Sopenharmony_ci regs->elm_location_config = elm_read_reg(info, ELM_LOCATION_CONFIG); 44562306a36Sopenharmony_ci regs->elm_page_ctrl = elm_read_reg(info, ELM_PAGE_CTRL); 44662306a36Sopenharmony_ci for (i = 0; i < ERROR_VECTOR_MAX; i++) { 44762306a36Sopenharmony_ci offset = i * SYNDROME_FRAGMENT_REG_SIZE; 44862306a36Sopenharmony_ci switch (bch_type) { 44962306a36Sopenharmony_ci case BCH16_ECC: 45062306a36Sopenharmony_ci regs->elm_syndrome_fragment_6[i] = elm_read_reg(info, 45162306a36Sopenharmony_ci ELM_SYNDROME_FRAGMENT_6 + offset); 45262306a36Sopenharmony_ci regs->elm_syndrome_fragment_5[i] = elm_read_reg(info, 45362306a36Sopenharmony_ci ELM_SYNDROME_FRAGMENT_5 + offset); 45462306a36Sopenharmony_ci regs->elm_syndrome_fragment_4[i] = elm_read_reg(info, 45562306a36Sopenharmony_ci ELM_SYNDROME_FRAGMENT_4 + offset); 45662306a36Sopenharmony_ci fallthrough; 45762306a36Sopenharmony_ci case BCH8_ECC: 45862306a36Sopenharmony_ci regs->elm_syndrome_fragment_3[i] = elm_read_reg(info, 45962306a36Sopenharmony_ci ELM_SYNDROME_FRAGMENT_3 + offset); 46062306a36Sopenharmony_ci regs->elm_syndrome_fragment_2[i] = elm_read_reg(info, 46162306a36Sopenharmony_ci ELM_SYNDROME_FRAGMENT_2 + offset); 46262306a36Sopenharmony_ci fallthrough; 46362306a36Sopenharmony_ci case BCH4_ECC: 46462306a36Sopenharmony_ci regs->elm_syndrome_fragment_1[i] = elm_read_reg(info, 46562306a36Sopenharmony_ci ELM_SYNDROME_FRAGMENT_1 + offset); 46662306a36Sopenharmony_ci regs->elm_syndrome_fragment_0[i] = elm_read_reg(info, 46762306a36Sopenharmony_ci ELM_SYNDROME_FRAGMENT_0 + offset); 46862306a36Sopenharmony_ci break; 46962306a36Sopenharmony_ci default: 47062306a36Sopenharmony_ci return -EINVAL; 47162306a36Sopenharmony_ci } 47262306a36Sopenharmony_ci /* ELM SYNDROME_VALID bit in SYNDROME_FRAGMENT_6[] needs 47362306a36Sopenharmony_ci * to be saved for all BCH schemes*/ 47462306a36Sopenharmony_ci regs->elm_syndrome_fragment_6[i] = elm_read_reg(info, 47562306a36Sopenharmony_ci ELM_SYNDROME_FRAGMENT_6 + offset); 47662306a36Sopenharmony_ci } 47762306a36Sopenharmony_ci return 0; 47862306a36Sopenharmony_ci} 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci/* 48162306a36Sopenharmony_ci * elm_context_restore 48262306a36Sopenharmony_ci * writes configurations saved duing power-down back into ELM registers 48362306a36Sopenharmony_ci */ 48462306a36Sopenharmony_cistatic int elm_context_restore(struct elm_info *info) 48562306a36Sopenharmony_ci{ 48662306a36Sopenharmony_ci struct elm_registers *regs = &info->elm_regs; 48762306a36Sopenharmony_ci enum bch_ecc bch_type = info->bch_type; 48862306a36Sopenharmony_ci u32 offset = 0, i; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci elm_write_reg(info, ELM_IRQENABLE, regs->elm_irqenable); 49162306a36Sopenharmony_ci elm_write_reg(info, ELM_SYSCONFIG, regs->elm_sysconfig); 49262306a36Sopenharmony_ci elm_write_reg(info, ELM_LOCATION_CONFIG, regs->elm_location_config); 49362306a36Sopenharmony_ci elm_write_reg(info, ELM_PAGE_CTRL, regs->elm_page_ctrl); 49462306a36Sopenharmony_ci for (i = 0; i < ERROR_VECTOR_MAX; i++) { 49562306a36Sopenharmony_ci offset = i * SYNDROME_FRAGMENT_REG_SIZE; 49662306a36Sopenharmony_ci switch (bch_type) { 49762306a36Sopenharmony_ci case BCH16_ECC: 49862306a36Sopenharmony_ci elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset, 49962306a36Sopenharmony_ci regs->elm_syndrome_fragment_6[i]); 50062306a36Sopenharmony_ci elm_write_reg(info, ELM_SYNDROME_FRAGMENT_5 + offset, 50162306a36Sopenharmony_ci regs->elm_syndrome_fragment_5[i]); 50262306a36Sopenharmony_ci elm_write_reg(info, ELM_SYNDROME_FRAGMENT_4 + offset, 50362306a36Sopenharmony_ci regs->elm_syndrome_fragment_4[i]); 50462306a36Sopenharmony_ci fallthrough; 50562306a36Sopenharmony_ci case BCH8_ECC: 50662306a36Sopenharmony_ci elm_write_reg(info, ELM_SYNDROME_FRAGMENT_3 + offset, 50762306a36Sopenharmony_ci regs->elm_syndrome_fragment_3[i]); 50862306a36Sopenharmony_ci elm_write_reg(info, ELM_SYNDROME_FRAGMENT_2 + offset, 50962306a36Sopenharmony_ci regs->elm_syndrome_fragment_2[i]); 51062306a36Sopenharmony_ci fallthrough; 51162306a36Sopenharmony_ci case BCH4_ECC: 51262306a36Sopenharmony_ci elm_write_reg(info, ELM_SYNDROME_FRAGMENT_1 + offset, 51362306a36Sopenharmony_ci regs->elm_syndrome_fragment_1[i]); 51462306a36Sopenharmony_ci elm_write_reg(info, ELM_SYNDROME_FRAGMENT_0 + offset, 51562306a36Sopenharmony_ci regs->elm_syndrome_fragment_0[i]); 51662306a36Sopenharmony_ci break; 51762306a36Sopenharmony_ci default: 51862306a36Sopenharmony_ci return -EINVAL; 51962306a36Sopenharmony_ci } 52062306a36Sopenharmony_ci /* ELM_SYNDROME_VALID bit to be set in last to trigger FSM */ 52162306a36Sopenharmony_ci elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset, 52262306a36Sopenharmony_ci regs->elm_syndrome_fragment_6[i] & 52362306a36Sopenharmony_ci ELM_SYNDROME_VALID); 52462306a36Sopenharmony_ci } 52562306a36Sopenharmony_ci return 0; 52662306a36Sopenharmony_ci} 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_cistatic int elm_suspend(struct device *dev) 52962306a36Sopenharmony_ci{ 53062306a36Sopenharmony_ci struct elm_info *info = dev_get_drvdata(dev); 53162306a36Sopenharmony_ci elm_context_save(info); 53262306a36Sopenharmony_ci pm_runtime_put_sync(dev); 53362306a36Sopenharmony_ci return 0; 53462306a36Sopenharmony_ci} 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_cistatic int elm_resume(struct device *dev) 53762306a36Sopenharmony_ci{ 53862306a36Sopenharmony_ci struct elm_info *info = dev_get_drvdata(dev); 53962306a36Sopenharmony_ci pm_runtime_get_sync(dev); 54062306a36Sopenharmony_ci elm_context_restore(info); 54162306a36Sopenharmony_ci return 0; 54262306a36Sopenharmony_ci} 54362306a36Sopenharmony_ci#endif 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(elm_pm_ops, elm_suspend, elm_resume); 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci#ifdef CONFIG_OF 54862306a36Sopenharmony_cistatic const struct of_device_id elm_of_match[] = { 54962306a36Sopenharmony_ci { .compatible = "ti,am3352-elm" }, 55062306a36Sopenharmony_ci { .compatible = "ti,am64-elm" }, 55162306a36Sopenharmony_ci {}, 55262306a36Sopenharmony_ci}; 55362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, elm_of_match); 55462306a36Sopenharmony_ci#endif 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_cistatic struct platform_driver elm_driver = { 55762306a36Sopenharmony_ci .driver = { 55862306a36Sopenharmony_ci .name = DRIVER_NAME, 55962306a36Sopenharmony_ci .of_match_table = of_match_ptr(elm_of_match), 56062306a36Sopenharmony_ci .pm = &elm_pm_ops, 56162306a36Sopenharmony_ci }, 56262306a36Sopenharmony_ci .probe = elm_probe, 56362306a36Sopenharmony_ci .remove_new = elm_remove, 56462306a36Sopenharmony_ci}; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_cimodule_platform_driver(elm_driver); 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ciMODULE_DESCRIPTION("ELM driver for BCH error correction"); 56962306a36Sopenharmony_ciMODULE_AUTHOR("Texas Instruments"); 57062306a36Sopenharmony_ciMODULE_ALIAS("platform:" DRIVER_NAME); 57162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 572