18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 OR MIT
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * MTK ECC controller driver.
48c2ecf20Sopenharmony_ci * Copyright (C) 2016  MediaTek Inc.
58c2ecf20Sopenharmony_ci * Authors:	Xiaolei Li		<xiaolei.li@mediatek.com>
68c2ecf20Sopenharmony_ci *		Jorge Ramirez-Ortiz	<jorge.ramirez-ortiz@linaro.org>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
108c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h>
118c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
128c2ecf20Sopenharmony_ci#include <linux/clk.h>
138c2ecf20Sopenharmony_ci#include <linux/module.h>
148c2ecf20Sopenharmony_ci#include <linux/iopoll.h>
158c2ecf20Sopenharmony_ci#include <linux/of.h>
168c2ecf20Sopenharmony_ci#include <linux/of_platform.h>
178c2ecf20Sopenharmony_ci#include <linux/mutex.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include "mtk_ecc.h"
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define ECC_IDLE_MASK		BIT(0)
228c2ecf20Sopenharmony_ci#define ECC_IRQ_EN		BIT(0)
238c2ecf20Sopenharmony_ci#define ECC_PG_IRQ_SEL		BIT(1)
248c2ecf20Sopenharmony_ci#define ECC_OP_ENABLE		(1)
258c2ecf20Sopenharmony_ci#define ECC_OP_DISABLE		(0)
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#define ECC_ENCCON		(0x00)
288c2ecf20Sopenharmony_ci#define ECC_ENCCNFG		(0x04)
298c2ecf20Sopenharmony_ci#define		ECC_MS_SHIFT		(16)
308c2ecf20Sopenharmony_ci#define ECC_ENCDIADDR		(0x08)
318c2ecf20Sopenharmony_ci#define ECC_ENCIDLE		(0x0C)
328c2ecf20Sopenharmony_ci#define ECC_DECCON		(0x100)
338c2ecf20Sopenharmony_ci#define ECC_DECCNFG		(0x104)
348c2ecf20Sopenharmony_ci#define		DEC_EMPTY_EN		BIT(31)
358c2ecf20Sopenharmony_ci#define		DEC_CNFG_CORRECT	(0x3 << 12)
368c2ecf20Sopenharmony_ci#define ECC_DECIDLE		(0x10C)
378c2ecf20Sopenharmony_ci#define ECC_DECENUM0		(0x114)
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci#define ECC_TIMEOUT		(500000)
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci#define ECC_IDLE_REG(op)	((op) == ECC_ENCODE ? ECC_ENCIDLE : ECC_DECIDLE)
428c2ecf20Sopenharmony_ci#define ECC_CTL_REG(op)		((op) == ECC_ENCODE ? ECC_ENCCON : ECC_DECCON)
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistruct mtk_ecc_caps {
458c2ecf20Sopenharmony_ci	u32 err_mask;
468c2ecf20Sopenharmony_ci	u32 err_shift;
478c2ecf20Sopenharmony_ci	const u8 *ecc_strength;
488c2ecf20Sopenharmony_ci	const u32 *ecc_regs;
498c2ecf20Sopenharmony_ci	u8 num_ecc_strength;
508c2ecf20Sopenharmony_ci	u8 ecc_mode_shift;
518c2ecf20Sopenharmony_ci	u32 parity_bits;
528c2ecf20Sopenharmony_ci	int pg_irq_sel;
538c2ecf20Sopenharmony_ci};
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cistruct mtk_ecc {
568c2ecf20Sopenharmony_ci	struct device *dev;
578c2ecf20Sopenharmony_ci	const struct mtk_ecc_caps *caps;
588c2ecf20Sopenharmony_ci	void __iomem *regs;
598c2ecf20Sopenharmony_ci	struct clk *clk;
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	struct completion done;
628c2ecf20Sopenharmony_ci	struct mutex lock;
638c2ecf20Sopenharmony_ci	u32 sectors;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	u8 *eccdata;
668c2ecf20Sopenharmony_ci};
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci/* ecc strength that each IP supports */
698c2ecf20Sopenharmony_cistatic const u8 ecc_strength_mt2701[] = {
708c2ecf20Sopenharmony_ci	4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36,
718c2ecf20Sopenharmony_ci	40, 44, 48, 52, 56, 60
728c2ecf20Sopenharmony_ci};
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistatic const u8 ecc_strength_mt2712[] = {
758c2ecf20Sopenharmony_ci	4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36,
768c2ecf20Sopenharmony_ci	40, 44, 48, 52, 56, 60, 68, 72, 80
778c2ecf20Sopenharmony_ci};
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_cistatic const u8 ecc_strength_mt7622[] = {
808c2ecf20Sopenharmony_ci	4, 6, 8, 10, 12
818c2ecf20Sopenharmony_ci};
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cienum mtk_ecc_regs {
848c2ecf20Sopenharmony_ci	ECC_ENCPAR00,
858c2ecf20Sopenharmony_ci	ECC_ENCIRQ_EN,
868c2ecf20Sopenharmony_ci	ECC_ENCIRQ_STA,
878c2ecf20Sopenharmony_ci	ECC_DECDONE,
888c2ecf20Sopenharmony_ci	ECC_DECIRQ_EN,
898c2ecf20Sopenharmony_ci	ECC_DECIRQ_STA,
908c2ecf20Sopenharmony_ci};
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic int mt2701_ecc_regs[] = {
938c2ecf20Sopenharmony_ci	[ECC_ENCPAR00] =        0x10,
948c2ecf20Sopenharmony_ci	[ECC_ENCIRQ_EN] =       0x80,
958c2ecf20Sopenharmony_ci	[ECC_ENCIRQ_STA] =      0x84,
968c2ecf20Sopenharmony_ci	[ECC_DECDONE] =         0x124,
978c2ecf20Sopenharmony_ci	[ECC_DECIRQ_EN] =       0x200,
988c2ecf20Sopenharmony_ci	[ECC_DECIRQ_STA] =      0x204,
998c2ecf20Sopenharmony_ci};
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic int mt2712_ecc_regs[] = {
1028c2ecf20Sopenharmony_ci	[ECC_ENCPAR00] =        0x300,
1038c2ecf20Sopenharmony_ci	[ECC_ENCIRQ_EN] =       0x80,
1048c2ecf20Sopenharmony_ci	[ECC_ENCIRQ_STA] =      0x84,
1058c2ecf20Sopenharmony_ci	[ECC_DECDONE] =         0x124,
1068c2ecf20Sopenharmony_ci	[ECC_DECIRQ_EN] =       0x200,
1078c2ecf20Sopenharmony_ci	[ECC_DECIRQ_STA] =      0x204,
1088c2ecf20Sopenharmony_ci};
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_cistatic int mt7622_ecc_regs[] = {
1118c2ecf20Sopenharmony_ci	[ECC_ENCPAR00] =        0x10,
1128c2ecf20Sopenharmony_ci	[ECC_ENCIRQ_EN] =       0x30,
1138c2ecf20Sopenharmony_ci	[ECC_ENCIRQ_STA] =      0x34,
1148c2ecf20Sopenharmony_ci	[ECC_DECDONE] =         0x11c,
1158c2ecf20Sopenharmony_ci	[ECC_DECIRQ_EN] =       0x140,
1168c2ecf20Sopenharmony_ci	[ECC_DECIRQ_STA] =      0x144,
1178c2ecf20Sopenharmony_ci};
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_cistatic inline void mtk_ecc_wait_idle(struct mtk_ecc *ecc,
1208c2ecf20Sopenharmony_ci				     enum mtk_ecc_operation op)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	struct device *dev = ecc->dev;
1238c2ecf20Sopenharmony_ci	u32 val;
1248c2ecf20Sopenharmony_ci	int ret;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	ret = readl_poll_timeout_atomic(ecc->regs + ECC_IDLE_REG(op), val,
1278c2ecf20Sopenharmony_ci					val & ECC_IDLE_MASK,
1288c2ecf20Sopenharmony_ci					10, ECC_TIMEOUT);
1298c2ecf20Sopenharmony_ci	if (ret)
1308c2ecf20Sopenharmony_ci		dev_warn(dev, "%s NOT idle\n",
1318c2ecf20Sopenharmony_ci			 op == ECC_ENCODE ? "encoder" : "decoder");
1328c2ecf20Sopenharmony_ci}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_cistatic irqreturn_t mtk_ecc_irq(int irq, void *id)
1358c2ecf20Sopenharmony_ci{
1368c2ecf20Sopenharmony_ci	struct mtk_ecc *ecc = id;
1378c2ecf20Sopenharmony_ci	u32 dec, enc;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	dec = readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECIRQ_STA])
1408c2ecf20Sopenharmony_ci		    & ECC_IRQ_EN;
1418c2ecf20Sopenharmony_ci	if (dec) {
1428c2ecf20Sopenharmony_ci		dec = readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECDONE]);
1438c2ecf20Sopenharmony_ci		if (dec & ecc->sectors) {
1448c2ecf20Sopenharmony_ci			/*
1458c2ecf20Sopenharmony_ci			 * Clear decode IRQ status once again to ensure that
1468c2ecf20Sopenharmony_ci			 * there will be no extra IRQ.
1478c2ecf20Sopenharmony_ci			 */
1488c2ecf20Sopenharmony_ci			readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECIRQ_STA]);
1498c2ecf20Sopenharmony_ci			ecc->sectors = 0;
1508c2ecf20Sopenharmony_ci			complete(&ecc->done);
1518c2ecf20Sopenharmony_ci		} else {
1528c2ecf20Sopenharmony_ci			return IRQ_HANDLED;
1538c2ecf20Sopenharmony_ci		}
1548c2ecf20Sopenharmony_ci	} else {
1558c2ecf20Sopenharmony_ci		enc = readl(ecc->regs + ecc->caps->ecc_regs[ECC_ENCIRQ_STA])
1568c2ecf20Sopenharmony_ci		      & ECC_IRQ_EN;
1578c2ecf20Sopenharmony_ci		if (enc)
1588c2ecf20Sopenharmony_ci			complete(&ecc->done);
1598c2ecf20Sopenharmony_ci		else
1608c2ecf20Sopenharmony_ci			return IRQ_NONE;
1618c2ecf20Sopenharmony_ci	}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1648c2ecf20Sopenharmony_ci}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_cistatic int mtk_ecc_config(struct mtk_ecc *ecc, struct mtk_ecc_config *config)
1678c2ecf20Sopenharmony_ci{
1688c2ecf20Sopenharmony_ci	u32 ecc_bit, dec_sz, enc_sz;
1698c2ecf20Sopenharmony_ci	u32 reg, i;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	for (i = 0; i < ecc->caps->num_ecc_strength; i++) {
1728c2ecf20Sopenharmony_ci		if (ecc->caps->ecc_strength[i] == config->strength)
1738c2ecf20Sopenharmony_ci			break;
1748c2ecf20Sopenharmony_ci	}
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	if (i == ecc->caps->num_ecc_strength) {
1778c2ecf20Sopenharmony_ci		dev_err(ecc->dev, "invalid ecc strength %d\n",
1788c2ecf20Sopenharmony_ci			config->strength);
1798c2ecf20Sopenharmony_ci		return -EINVAL;
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	ecc_bit = i;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	if (config->op == ECC_ENCODE) {
1858c2ecf20Sopenharmony_ci		/* configure ECC encoder (in bits) */
1868c2ecf20Sopenharmony_ci		enc_sz = config->len << 3;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci		reg = ecc_bit | (config->mode << ecc->caps->ecc_mode_shift);
1898c2ecf20Sopenharmony_ci		reg |= (enc_sz << ECC_MS_SHIFT);
1908c2ecf20Sopenharmony_ci		writel(reg, ecc->regs + ECC_ENCCNFG);
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci		if (config->mode != ECC_NFI_MODE)
1938c2ecf20Sopenharmony_ci			writel(lower_32_bits(config->addr),
1948c2ecf20Sopenharmony_ci			       ecc->regs + ECC_ENCDIADDR);
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	} else {
1978c2ecf20Sopenharmony_ci		/* configure ECC decoder (in bits) */
1988c2ecf20Sopenharmony_ci		dec_sz = (config->len << 3) +
1998c2ecf20Sopenharmony_ci			 config->strength * ecc->caps->parity_bits;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci		reg = ecc_bit | (config->mode << ecc->caps->ecc_mode_shift);
2028c2ecf20Sopenharmony_ci		reg |= (dec_sz << ECC_MS_SHIFT) | DEC_CNFG_CORRECT;
2038c2ecf20Sopenharmony_ci		reg |= DEC_EMPTY_EN;
2048c2ecf20Sopenharmony_ci		writel(reg, ecc->regs + ECC_DECCNFG);
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci		if (config->sectors)
2078c2ecf20Sopenharmony_ci			ecc->sectors = 1 << (config->sectors - 1);
2088c2ecf20Sopenharmony_ci	}
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	return 0;
2118c2ecf20Sopenharmony_ci}
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_civoid mtk_ecc_get_stats(struct mtk_ecc *ecc, struct mtk_ecc_stats *stats,
2148c2ecf20Sopenharmony_ci		       int sectors)
2158c2ecf20Sopenharmony_ci{
2168c2ecf20Sopenharmony_ci	u32 offset, i, err;
2178c2ecf20Sopenharmony_ci	u32 bitflips = 0;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	stats->corrected = 0;
2208c2ecf20Sopenharmony_ci	stats->failed = 0;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	for (i = 0; i < sectors; i++) {
2238c2ecf20Sopenharmony_ci		offset = (i >> 2) << 2;
2248c2ecf20Sopenharmony_ci		err = readl(ecc->regs + ECC_DECENUM0 + offset);
2258c2ecf20Sopenharmony_ci		err = err >> ((i % 4) * ecc->caps->err_shift);
2268c2ecf20Sopenharmony_ci		err &= ecc->caps->err_mask;
2278c2ecf20Sopenharmony_ci		if (err == ecc->caps->err_mask) {
2288c2ecf20Sopenharmony_ci			/* uncorrectable errors */
2298c2ecf20Sopenharmony_ci			stats->failed++;
2308c2ecf20Sopenharmony_ci			continue;
2318c2ecf20Sopenharmony_ci		}
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci		stats->corrected += err;
2348c2ecf20Sopenharmony_ci		bitflips = max_t(u32, bitflips, err);
2358c2ecf20Sopenharmony_ci	}
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	stats->bitflips = bitflips;
2388c2ecf20Sopenharmony_ci}
2398c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mtk_ecc_get_stats);
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_civoid mtk_ecc_release(struct mtk_ecc *ecc)
2428c2ecf20Sopenharmony_ci{
2438c2ecf20Sopenharmony_ci	clk_disable_unprepare(ecc->clk);
2448c2ecf20Sopenharmony_ci	put_device(ecc->dev);
2458c2ecf20Sopenharmony_ci}
2468c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mtk_ecc_release);
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_cistatic void mtk_ecc_hw_init(struct mtk_ecc *ecc)
2498c2ecf20Sopenharmony_ci{
2508c2ecf20Sopenharmony_ci	mtk_ecc_wait_idle(ecc, ECC_ENCODE);
2518c2ecf20Sopenharmony_ci	writew(ECC_OP_DISABLE, ecc->regs + ECC_ENCCON);
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	mtk_ecc_wait_idle(ecc, ECC_DECODE);
2548c2ecf20Sopenharmony_ci	writel(ECC_OP_DISABLE, ecc->regs + ECC_DECCON);
2558c2ecf20Sopenharmony_ci}
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_cistatic struct mtk_ecc *mtk_ecc_get(struct device_node *np)
2588c2ecf20Sopenharmony_ci{
2598c2ecf20Sopenharmony_ci	struct platform_device *pdev;
2608c2ecf20Sopenharmony_ci	struct mtk_ecc *ecc;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	pdev = of_find_device_by_node(np);
2638c2ecf20Sopenharmony_ci	if (!pdev)
2648c2ecf20Sopenharmony_ci		return ERR_PTR(-EPROBE_DEFER);
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	ecc = platform_get_drvdata(pdev);
2678c2ecf20Sopenharmony_ci	if (!ecc) {
2688c2ecf20Sopenharmony_ci		put_device(&pdev->dev);
2698c2ecf20Sopenharmony_ci		return ERR_PTR(-EPROBE_DEFER);
2708c2ecf20Sopenharmony_ci	}
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	clk_prepare_enable(ecc->clk);
2738c2ecf20Sopenharmony_ci	mtk_ecc_hw_init(ecc);
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	return ecc;
2768c2ecf20Sopenharmony_ci}
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_cistruct mtk_ecc *of_mtk_ecc_get(struct device_node *of_node)
2798c2ecf20Sopenharmony_ci{
2808c2ecf20Sopenharmony_ci	struct mtk_ecc *ecc = NULL;
2818c2ecf20Sopenharmony_ci	struct device_node *np;
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	np = of_parse_phandle(of_node, "ecc-engine", 0);
2848c2ecf20Sopenharmony_ci	if (np) {
2858c2ecf20Sopenharmony_ci		ecc = mtk_ecc_get(np);
2868c2ecf20Sopenharmony_ci		of_node_put(np);
2878c2ecf20Sopenharmony_ci	}
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci	return ecc;
2908c2ecf20Sopenharmony_ci}
2918c2ecf20Sopenharmony_ciEXPORT_SYMBOL(of_mtk_ecc_get);
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ciint mtk_ecc_enable(struct mtk_ecc *ecc, struct mtk_ecc_config *config)
2948c2ecf20Sopenharmony_ci{
2958c2ecf20Sopenharmony_ci	enum mtk_ecc_operation op = config->op;
2968c2ecf20Sopenharmony_ci	u16 reg_val;
2978c2ecf20Sopenharmony_ci	int ret;
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci	ret = mutex_lock_interruptible(&ecc->lock);
3008c2ecf20Sopenharmony_ci	if (ret) {
3018c2ecf20Sopenharmony_ci		dev_err(ecc->dev, "interrupted when attempting to lock\n");
3028c2ecf20Sopenharmony_ci		return ret;
3038c2ecf20Sopenharmony_ci	}
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci	mtk_ecc_wait_idle(ecc, op);
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_ci	ret = mtk_ecc_config(ecc, config);
3088c2ecf20Sopenharmony_ci	if (ret) {
3098c2ecf20Sopenharmony_ci		mutex_unlock(&ecc->lock);
3108c2ecf20Sopenharmony_ci		return ret;
3118c2ecf20Sopenharmony_ci	}
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	if (config->mode != ECC_NFI_MODE || op != ECC_ENCODE) {
3148c2ecf20Sopenharmony_ci		init_completion(&ecc->done);
3158c2ecf20Sopenharmony_ci		reg_val = ECC_IRQ_EN;
3168c2ecf20Sopenharmony_ci		/*
3178c2ecf20Sopenharmony_ci		 * For ECC_NFI_MODE, if ecc->caps->pg_irq_sel is 1, then it
3188c2ecf20Sopenharmony_ci		 * means this chip can only generate one ecc irq during page
3198c2ecf20Sopenharmony_ci		 * read / write. If is 0, generate one ecc irq each ecc step.
3208c2ecf20Sopenharmony_ci		 */
3218c2ecf20Sopenharmony_ci		if (ecc->caps->pg_irq_sel && config->mode == ECC_NFI_MODE)
3228c2ecf20Sopenharmony_ci			reg_val |= ECC_PG_IRQ_SEL;
3238c2ecf20Sopenharmony_ci		if (op == ECC_ENCODE)
3248c2ecf20Sopenharmony_ci			writew(reg_val, ecc->regs +
3258c2ecf20Sopenharmony_ci			       ecc->caps->ecc_regs[ECC_ENCIRQ_EN]);
3268c2ecf20Sopenharmony_ci		else
3278c2ecf20Sopenharmony_ci			writew(reg_val, ecc->regs +
3288c2ecf20Sopenharmony_ci			       ecc->caps->ecc_regs[ECC_DECIRQ_EN]);
3298c2ecf20Sopenharmony_ci	}
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci	writew(ECC_OP_ENABLE, ecc->regs + ECC_CTL_REG(op));
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_ci	return 0;
3348c2ecf20Sopenharmony_ci}
3358c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mtk_ecc_enable);
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_civoid mtk_ecc_disable(struct mtk_ecc *ecc)
3388c2ecf20Sopenharmony_ci{
3398c2ecf20Sopenharmony_ci	enum mtk_ecc_operation op = ECC_ENCODE;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	/* find out the running operation */
3428c2ecf20Sopenharmony_ci	if (readw(ecc->regs + ECC_CTL_REG(op)) != ECC_OP_ENABLE)
3438c2ecf20Sopenharmony_ci		op = ECC_DECODE;
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci	/* disable it */
3468c2ecf20Sopenharmony_ci	mtk_ecc_wait_idle(ecc, op);
3478c2ecf20Sopenharmony_ci	if (op == ECC_DECODE) {
3488c2ecf20Sopenharmony_ci		/*
3498c2ecf20Sopenharmony_ci		 * Clear decode IRQ status in case there is a timeout to wait
3508c2ecf20Sopenharmony_ci		 * decode IRQ.
3518c2ecf20Sopenharmony_ci		 */
3528c2ecf20Sopenharmony_ci		readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECDONE]);
3538c2ecf20Sopenharmony_ci		writew(0, ecc->regs + ecc->caps->ecc_regs[ECC_DECIRQ_EN]);
3548c2ecf20Sopenharmony_ci	} else {
3558c2ecf20Sopenharmony_ci		writew(0, ecc->regs + ecc->caps->ecc_regs[ECC_ENCIRQ_EN]);
3568c2ecf20Sopenharmony_ci	}
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	writew(ECC_OP_DISABLE, ecc->regs + ECC_CTL_REG(op));
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ci	mutex_unlock(&ecc->lock);
3618c2ecf20Sopenharmony_ci}
3628c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mtk_ecc_disable);
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_ciint mtk_ecc_wait_done(struct mtk_ecc *ecc, enum mtk_ecc_operation op)
3658c2ecf20Sopenharmony_ci{
3668c2ecf20Sopenharmony_ci	int ret;
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci	ret = wait_for_completion_timeout(&ecc->done, msecs_to_jiffies(500));
3698c2ecf20Sopenharmony_ci	if (!ret) {
3708c2ecf20Sopenharmony_ci		dev_err(ecc->dev, "%s timeout - interrupt did not arrive)\n",
3718c2ecf20Sopenharmony_ci			(op == ECC_ENCODE) ? "encoder" : "decoder");
3728c2ecf20Sopenharmony_ci		return -ETIMEDOUT;
3738c2ecf20Sopenharmony_ci	}
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_ci	return 0;
3768c2ecf20Sopenharmony_ci}
3778c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mtk_ecc_wait_done);
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ciint mtk_ecc_encode(struct mtk_ecc *ecc, struct mtk_ecc_config *config,
3808c2ecf20Sopenharmony_ci		   u8 *data, u32 bytes)
3818c2ecf20Sopenharmony_ci{
3828c2ecf20Sopenharmony_ci	dma_addr_t addr;
3838c2ecf20Sopenharmony_ci	u32 len;
3848c2ecf20Sopenharmony_ci	int ret;
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_ci	addr = dma_map_single(ecc->dev, data, bytes, DMA_TO_DEVICE);
3878c2ecf20Sopenharmony_ci	ret = dma_mapping_error(ecc->dev, addr);
3888c2ecf20Sopenharmony_ci	if (ret) {
3898c2ecf20Sopenharmony_ci		dev_err(ecc->dev, "dma mapping error\n");
3908c2ecf20Sopenharmony_ci		return -EINVAL;
3918c2ecf20Sopenharmony_ci	}
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_ci	config->op = ECC_ENCODE;
3948c2ecf20Sopenharmony_ci	config->addr = addr;
3958c2ecf20Sopenharmony_ci	ret = mtk_ecc_enable(ecc, config);
3968c2ecf20Sopenharmony_ci	if (ret) {
3978c2ecf20Sopenharmony_ci		dma_unmap_single(ecc->dev, addr, bytes, DMA_TO_DEVICE);
3988c2ecf20Sopenharmony_ci		return ret;
3998c2ecf20Sopenharmony_ci	}
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_ci	ret = mtk_ecc_wait_done(ecc, ECC_ENCODE);
4028c2ecf20Sopenharmony_ci	if (ret)
4038c2ecf20Sopenharmony_ci		goto timeout;
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_ci	mtk_ecc_wait_idle(ecc, ECC_ENCODE);
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_ci	/* Program ECC bytes to OOB: per sector oob = FDM + ECC + SPARE */
4088c2ecf20Sopenharmony_ci	len = (config->strength * ecc->caps->parity_bits + 7) >> 3;
4098c2ecf20Sopenharmony_ci
4108c2ecf20Sopenharmony_ci	/* write the parity bytes generated by the ECC back to temp buffer */
4118c2ecf20Sopenharmony_ci	__ioread32_copy(ecc->eccdata,
4128c2ecf20Sopenharmony_ci			ecc->regs + ecc->caps->ecc_regs[ECC_ENCPAR00],
4138c2ecf20Sopenharmony_ci			round_up(len, 4));
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_ci	/* copy into possibly unaligned OOB region with actual length */
4168c2ecf20Sopenharmony_ci	memcpy(data + bytes, ecc->eccdata, len);
4178c2ecf20Sopenharmony_citimeout:
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_ci	dma_unmap_single(ecc->dev, addr, bytes, DMA_TO_DEVICE);
4208c2ecf20Sopenharmony_ci	mtk_ecc_disable(ecc);
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_ci	return ret;
4238c2ecf20Sopenharmony_ci}
4248c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mtk_ecc_encode);
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_civoid mtk_ecc_adjust_strength(struct mtk_ecc *ecc, u32 *p)
4278c2ecf20Sopenharmony_ci{
4288c2ecf20Sopenharmony_ci	const u8 *ecc_strength = ecc->caps->ecc_strength;
4298c2ecf20Sopenharmony_ci	int i;
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci	for (i = 0; i < ecc->caps->num_ecc_strength; i++) {
4328c2ecf20Sopenharmony_ci		if (*p <= ecc_strength[i]) {
4338c2ecf20Sopenharmony_ci			if (!i)
4348c2ecf20Sopenharmony_ci				*p = ecc_strength[i];
4358c2ecf20Sopenharmony_ci			else if (*p != ecc_strength[i])
4368c2ecf20Sopenharmony_ci				*p = ecc_strength[i - 1];
4378c2ecf20Sopenharmony_ci			return;
4388c2ecf20Sopenharmony_ci		}
4398c2ecf20Sopenharmony_ci	}
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	*p = ecc_strength[ecc->caps->num_ecc_strength - 1];
4428c2ecf20Sopenharmony_ci}
4438c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mtk_ecc_adjust_strength);
4448c2ecf20Sopenharmony_ci
4458c2ecf20Sopenharmony_ciunsigned int mtk_ecc_get_parity_bits(struct mtk_ecc *ecc)
4468c2ecf20Sopenharmony_ci{
4478c2ecf20Sopenharmony_ci	return ecc->caps->parity_bits;
4488c2ecf20Sopenharmony_ci}
4498c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mtk_ecc_get_parity_bits);
4508c2ecf20Sopenharmony_ci
4518c2ecf20Sopenharmony_cistatic const struct mtk_ecc_caps mtk_ecc_caps_mt2701 = {
4528c2ecf20Sopenharmony_ci	.err_mask = 0x3f,
4538c2ecf20Sopenharmony_ci	.err_shift = 8,
4548c2ecf20Sopenharmony_ci	.ecc_strength = ecc_strength_mt2701,
4558c2ecf20Sopenharmony_ci	.ecc_regs = mt2701_ecc_regs,
4568c2ecf20Sopenharmony_ci	.num_ecc_strength = 20,
4578c2ecf20Sopenharmony_ci	.ecc_mode_shift = 5,
4588c2ecf20Sopenharmony_ci	.parity_bits = 14,
4598c2ecf20Sopenharmony_ci	.pg_irq_sel = 0,
4608c2ecf20Sopenharmony_ci};
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_cistatic const struct mtk_ecc_caps mtk_ecc_caps_mt2712 = {
4638c2ecf20Sopenharmony_ci	.err_mask = 0x7f,
4648c2ecf20Sopenharmony_ci	.err_shift = 8,
4658c2ecf20Sopenharmony_ci	.ecc_strength = ecc_strength_mt2712,
4668c2ecf20Sopenharmony_ci	.ecc_regs = mt2712_ecc_regs,
4678c2ecf20Sopenharmony_ci	.num_ecc_strength = 23,
4688c2ecf20Sopenharmony_ci	.ecc_mode_shift = 5,
4698c2ecf20Sopenharmony_ci	.parity_bits = 14,
4708c2ecf20Sopenharmony_ci	.pg_irq_sel = 1,
4718c2ecf20Sopenharmony_ci};
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_cistatic const struct mtk_ecc_caps mtk_ecc_caps_mt7622 = {
4748c2ecf20Sopenharmony_ci	.err_mask = 0x1f,
4758c2ecf20Sopenharmony_ci	.err_shift = 5,
4768c2ecf20Sopenharmony_ci	.ecc_strength = ecc_strength_mt7622,
4778c2ecf20Sopenharmony_ci	.ecc_regs = mt7622_ecc_regs,
4788c2ecf20Sopenharmony_ci	.num_ecc_strength = 5,
4798c2ecf20Sopenharmony_ci	.ecc_mode_shift = 4,
4808c2ecf20Sopenharmony_ci	.parity_bits = 13,
4818c2ecf20Sopenharmony_ci	.pg_irq_sel = 0,
4828c2ecf20Sopenharmony_ci};
4838c2ecf20Sopenharmony_ci
4848c2ecf20Sopenharmony_cistatic const struct of_device_id mtk_ecc_dt_match[] = {
4858c2ecf20Sopenharmony_ci	{
4868c2ecf20Sopenharmony_ci		.compatible = "mediatek,mt2701-ecc",
4878c2ecf20Sopenharmony_ci		.data = &mtk_ecc_caps_mt2701,
4888c2ecf20Sopenharmony_ci	}, {
4898c2ecf20Sopenharmony_ci		.compatible = "mediatek,mt2712-ecc",
4908c2ecf20Sopenharmony_ci		.data = &mtk_ecc_caps_mt2712,
4918c2ecf20Sopenharmony_ci	}, {
4928c2ecf20Sopenharmony_ci		.compatible = "mediatek,mt7622-ecc",
4938c2ecf20Sopenharmony_ci		.data = &mtk_ecc_caps_mt7622,
4948c2ecf20Sopenharmony_ci	},
4958c2ecf20Sopenharmony_ci	{},
4968c2ecf20Sopenharmony_ci};
4978c2ecf20Sopenharmony_ci
4988c2ecf20Sopenharmony_cistatic int mtk_ecc_probe(struct platform_device *pdev)
4998c2ecf20Sopenharmony_ci{
5008c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
5018c2ecf20Sopenharmony_ci	struct mtk_ecc *ecc;
5028c2ecf20Sopenharmony_ci	struct resource *res;
5038c2ecf20Sopenharmony_ci	u32 max_eccdata_size;
5048c2ecf20Sopenharmony_ci	int irq, ret;
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_ci	ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL);
5078c2ecf20Sopenharmony_ci	if (!ecc)
5088c2ecf20Sopenharmony_ci		return -ENOMEM;
5098c2ecf20Sopenharmony_ci
5108c2ecf20Sopenharmony_ci	ecc->caps = of_device_get_match_data(dev);
5118c2ecf20Sopenharmony_ci
5128c2ecf20Sopenharmony_ci	max_eccdata_size = ecc->caps->num_ecc_strength - 1;
5138c2ecf20Sopenharmony_ci	max_eccdata_size = ecc->caps->ecc_strength[max_eccdata_size];
5148c2ecf20Sopenharmony_ci	max_eccdata_size = (max_eccdata_size * ecc->caps->parity_bits + 7) >> 3;
5158c2ecf20Sopenharmony_ci	max_eccdata_size = round_up(max_eccdata_size, 4);
5168c2ecf20Sopenharmony_ci	ecc->eccdata = devm_kzalloc(dev, max_eccdata_size, GFP_KERNEL);
5178c2ecf20Sopenharmony_ci	if (!ecc->eccdata)
5188c2ecf20Sopenharmony_ci		return -ENOMEM;
5198c2ecf20Sopenharmony_ci
5208c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
5218c2ecf20Sopenharmony_ci	ecc->regs = devm_ioremap_resource(dev, res);
5228c2ecf20Sopenharmony_ci	if (IS_ERR(ecc->regs)) {
5238c2ecf20Sopenharmony_ci		dev_err(dev, "failed to map regs: %ld\n", PTR_ERR(ecc->regs));
5248c2ecf20Sopenharmony_ci		return PTR_ERR(ecc->regs);
5258c2ecf20Sopenharmony_ci	}
5268c2ecf20Sopenharmony_ci
5278c2ecf20Sopenharmony_ci	ecc->clk = devm_clk_get(dev, NULL);
5288c2ecf20Sopenharmony_ci	if (IS_ERR(ecc->clk)) {
5298c2ecf20Sopenharmony_ci		dev_err(dev, "failed to get clock: %ld\n", PTR_ERR(ecc->clk));
5308c2ecf20Sopenharmony_ci		return PTR_ERR(ecc->clk);
5318c2ecf20Sopenharmony_ci	}
5328c2ecf20Sopenharmony_ci
5338c2ecf20Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
5348c2ecf20Sopenharmony_ci	if (irq < 0)
5358c2ecf20Sopenharmony_ci		return irq;
5368c2ecf20Sopenharmony_ci
5378c2ecf20Sopenharmony_ci	ret = dma_set_mask(dev, DMA_BIT_MASK(32));
5388c2ecf20Sopenharmony_ci	if (ret) {
5398c2ecf20Sopenharmony_ci		dev_err(dev, "failed to set DMA mask\n");
5408c2ecf20Sopenharmony_ci		return ret;
5418c2ecf20Sopenharmony_ci	}
5428c2ecf20Sopenharmony_ci
5438c2ecf20Sopenharmony_ci	ret = devm_request_irq(dev, irq, mtk_ecc_irq, 0x0, "mtk-ecc", ecc);
5448c2ecf20Sopenharmony_ci	if (ret) {
5458c2ecf20Sopenharmony_ci		dev_err(dev, "failed to request irq\n");
5468c2ecf20Sopenharmony_ci		return -EINVAL;
5478c2ecf20Sopenharmony_ci	}
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci	ecc->dev = dev;
5508c2ecf20Sopenharmony_ci	mutex_init(&ecc->lock);
5518c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, ecc);
5528c2ecf20Sopenharmony_ci	dev_info(dev, "probed\n");
5538c2ecf20Sopenharmony_ci
5548c2ecf20Sopenharmony_ci	return 0;
5558c2ecf20Sopenharmony_ci}
5568c2ecf20Sopenharmony_ci
5578c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
5588c2ecf20Sopenharmony_cistatic int mtk_ecc_suspend(struct device *dev)
5598c2ecf20Sopenharmony_ci{
5608c2ecf20Sopenharmony_ci	struct mtk_ecc *ecc = dev_get_drvdata(dev);
5618c2ecf20Sopenharmony_ci
5628c2ecf20Sopenharmony_ci	clk_disable_unprepare(ecc->clk);
5638c2ecf20Sopenharmony_ci
5648c2ecf20Sopenharmony_ci	return 0;
5658c2ecf20Sopenharmony_ci}
5668c2ecf20Sopenharmony_ci
5678c2ecf20Sopenharmony_cistatic int mtk_ecc_resume(struct device *dev)
5688c2ecf20Sopenharmony_ci{
5698c2ecf20Sopenharmony_ci	struct mtk_ecc *ecc = dev_get_drvdata(dev);
5708c2ecf20Sopenharmony_ci	int ret;
5718c2ecf20Sopenharmony_ci
5728c2ecf20Sopenharmony_ci	ret = clk_prepare_enable(ecc->clk);
5738c2ecf20Sopenharmony_ci	if (ret) {
5748c2ecf20Sopenharmony_ci		dev_err(dev, "failed to enable clk\n");
5758c2ecf20Sopenharmony_ci		return ret;
5768c2ecf20Sopenharmony_ci	}
5778c2ecf20Sopenharmony_ci
5788c2ecf20Sopenharmony_ci	return 0;
5798c2ecf20Sopenharmony_ci}
5808c2ecf20Sopenharmony_ci
5818c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(mtk_ecc_pm_ops, mtk_ecc_suspend, mtk_ecc_resume);
5828c2ecf20Sopenharmony_ci#endif
5838c2ecf20Sopenharmony_ci
5848c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, mtk_ecc_dt_match);
5858c2ecf20Sopenharmony_ci
5868c2ecf20Sopenharmony_cistatic struct platform_driver mtk_ecc_driver = {
5878c2ecf20Sopenharmony_ci	.probe  = mtk_ecc_probe,
5888c2ecf20Sopenharmony_ci	.driver = {
5898c2ecf20Sopenharmony_ci		.name  = "mtk-ecc",
5908c2ecf20Sopenharmony_ci		.of_match_table = of_match_ptr(mtk_ecc_dt_match),
5918c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
5928c2ecf20Sopenharmony_ci		.pm = &mtk_ecc_pm_ops,
5938c2ecf20Sopenharmony_ci#endif
5948c2ecf20Sopenharmony_ci	},
5958c2ecf20Sopenharmony_ci};
5968c2ecf20Sopenharmony_ci
5978c2ecf20Sopenharmony_cimodule_platform_driver(mtk_ecc_driver);
5988c2ecf20Sopenharmony_ci
5998c2ecf20Sopenharmony_ciMODULE_AUTHOR("Xiaolei Li <xiaolei.li@mediatek.com>");
6008c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MTK Nand ECC Driver");
6018c2ecf20Sopenharmony_ciMODULE_LICENSE("Dual MIT/GPL");
602