summaryrefslogtreecommitdiffstats
path: root/drivers/mtd/nand/raw/ingenic/ingenic_ecc.c
blob: efe0ffe4f1abc87d0446f322fe17bea92c820474 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// SPDX-License-Identifier: GPL-2.0
/*
 * JZ47xx ECC common code
 *
 * Copyright (c) 2015 Imagination Technologies
 * Author: Alex Smith <alex.smith@imgtec.com>
 */

#include <linux/clk.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>

#include "ingenic_ecc.h"

/**
 * ingenic_ecc_calculate() - calculate ECC for a data buffer
 * @ecc: ECC device.
 * @params: ECC parameters.
 * @buf: input buffer with raw data.
 * @ecc_code: output buffer with ECC.
 *
 * Return: 0 on success, -ETIMEDOUT if timed out while waiting for ECC
 * controller.
 */
int ingenic_ecc_calculate(struct ingenic_ecc *ecc,
			  struct ingenic_ecc_params *params,
			  const u8 *buf, u8 *ecc_code)
{
	return ecc->ops->calculate(ecc, params, buf, ecc_code);
}

/**
 * ingenic_ecc_correct() - detect and correct bit errors
 * @ecc: ECC device.
 * @params: ECC parameters.
 * @buf: raw data read from the chip.
 * @ecc_code: ECC read from the chip.
 *
 * Given the raw data and the ECC read from the NAND device, detects and
 * corrects errors in the data.
 *
 * Return: the number of bit errors corrected, -EBADMSG if there are too many
 * errors to correct or -ETIMEDOUT if we timed out waiting for the controller.
 */
int ingenic_ecc_correct(struct ingenic_ecc *ecc,
			struct ingenic_ecc_params *params,
			u8 *buf, u8 *ecc_code)
{
	return ecc->ops->correct(ecc, params, buf, ecc_code);
}

/**
 * ingenic_ecc_get() - get the ECC controller device
 * @np: ECC device tree node.
 *
 * Gets the ECC controller device from the specified device tree node. The
 * device must be released with ingenic_ecc_release() when it is no longer being
 * used.
 *
 * Return: a pointer to ingenic_ecc, errors are encoded into the pointer.
 * PTR_ERR(-EPROBE_DEFER) if the device hasn't been initialised yet.
 */
static struct ingenic_ecc *ingenic_ecc_get(struct device_node *np)
{
	struct platform_device *pdev;
	struct ingenic_ecc *ecc;

	pdev = of_find_device_by_node(np);
	if (!pdev || !platform_get_drvdata(pdev))
		return ERR_PTR(-EPROBE_DEFER);

	ecc = platform_get_drvdata(pdev);
	clk_prepare_enable(ecc->clk);

	return ecc;
}

/**
 * of_ingenic_ecc_get() - get the ECC controller from a DT node
 * @of_node: the node that contains an ecc-engine property.
 *
 * Get the ecc-engine property from the given device tree
 * node and pass it to ingenic_ecc_get to do the work.
 *
 * Return: a pointer to ingenic_ecc, errors are encoded into the pointer.
 * PTR_ERR(-EPROBE_DEFER) if the device hasn't been initialised yet.
 */
struct ingenic_ecc *of_ingenic_ecc_get(struct device_node *of_node)
{
	struct ingenic_ecc *ecc = NULL;
	struct device_node *np;

	np = of_parse_phandle(of_node, "ecc-engine", 0);

	/*
	 * If the ecc-engine property is not found, check for the deprecated
	 * ingenic,bch-controller property
	 */
	if (!np)
		np = of_parse_phandle(of_node, "ingenic,bch-controller", 0);

	if (np) {
		ecc = ingenic_ecc_get(np);
		of_node_put(np);
	}
	return ecc;
}

/**
 * ingenic_ecc_release() - release the ECC controller device
 * @ecc: ECC device.
 */
void ingenic_ecc_release(struct ingenic_ecc *ecc)
{
	clk_disable_unprepare(ecc->clk);
	put_device(ecc->dev);
}

int ingenic_ecc_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct ingenic_ecc *ecc;

	ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL);
	if (!ecc)
		return -ENOMEM;

	ecc->ops = device_get_match_data(dev);
	if (!ecc->ops)
		return -EINVAL;

	ecc->base = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(ecc->base))
		return PTR_ERR(ecc->base);

	ecc->ops->disable(ecc);

	ecc->clk = devm_clk_get(dev, NULL);
	if (IS_ERR(ecc->clk)) {
		dev_err(dev, "failed to get clock: %ld\n", PTR_ERR(ecc->clk));
		return PTR_ERR(ecc->clk);
	}

	mutex_init(&ecc->lock);

	ecc->dev = dev;
	platform_set_drvdata(pdev, ecc);

	return 0;
}
EXPORT_SYMBOL(ingenic_ecc_probe);