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