summaryrefslogtreecommitdiff
path: root/arch/arm/mach-socfpga/ocram.c
blob: 10d673252395fb4ba15ec69996b00ce9b6c99cad (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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/*
 * Copyright Altera Corporation (C) 2016. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/genalloc.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>

#include "core.h"

#define ALTR_OCRAM_CLEAR_ECC          0x00000018
#define ALTR_OCRAM_ECC_EN             0x00000019

void socfpga_init_ocram_ecc(void)
{
	struct device_node *np;
	void __iomem *mapped_ocr_edac_addr;

	/* Find the OCRAM EDAC device tree node */
	np = of_find_compatible_node(NULL, NULL, "altr,socfpga-ocram-ecc");
	if (!np) {
		pr_err("Unable to find socfpga-ocram-ecc\n");
		return;
	}

	mapped_ocr_edac_addr = of_iomap(np, 0);
	of_node_put(np);
	if (!mapped_ocr_edac_addr) {
		pr_err("Unable to map OCRAM ecc regs.\n");
		return;
	}

	/* Clear any pending OCRAM ECC interrupts, then enable ECC */
	writel(ALTR_OCRAM_CLEAR_ECC, mapped_ocr_edac_addr);
	writel(ALTR_OCRAM_ECC_EN, mapped_ocr_edac_addr);

	iounmap(mapped_ocr_edac_addr);
}

/* Arria10 OCRAM Section */
#define ALTR_A10_ECC_CTRL_OFST          0x08
#define ALTR_A10_OCRAM_ECC_EN_CTL       (BIT(1) | BIT(0))
#define ALTR_A10_ECC_INITA              BIT(16)

#define ALTR_A10_ECC_INITSTAT_OFST      0x0C
#define ALTR_A10_ECC_INITCOMPLETEA      BIT(0)
#define ALTR_A10_ECC_INITCOMPLETEB      BIT(8)

#define ALTR_A10_ECC_ERRINTEN_OFST      0x10
#define ALTR_A10_ECC_SERRINTEN          BIT(0)

#define ALTR_A10_ECC_INTSTAT_OFST       0x20
#define ALTR_A10_ECC_SERRPENA           BIT(0)
#define ALTR_A10_ECC_DERRPENA           BIT(8)
#define ALTR_A10_ECC_ERRPENA_MASK       (ALTR_A10_ECC_SERRPENA | \
					 ALTR_A10_ECC_DERRPENA)
/* ECC Manager Defines */
#define A10_SYSMGR_ECC_INTMASK_SET_OFST   0x94
#define A10_SYSMGR_ECC_INTMASK_CLR_OFST   0x98
#define A10_SYSMGR_ECC_INTMASK_OCRAM      BIT(1)

#define ALTR_A10_ECC_INIT_WATCHDOG_10US   10000

static inline void ecc_set_bits(u32 bit_mask, void __iomem *ioaddr)
{
	u32 value = readl(ioaddr);

	value |= bit_mask;
	writel(value, ioaddr);
}

static inline void ecc_clear_bits(u32 bit_mask, void __iomem *ioaddr)
{
	u32 value = readl(ioaddr);

	value &= ~bit_mask;
	writel(value, ioaddr);
}

static inline int ecc_test_bits(u32 bit_mask, void __iomem *ioaddr)
{
	u32 value = readl(ioaddr);

	return (value & bit_mask) ? 1 : 0;
}

/*
 * This function uses the memory initialization block in the Arria10 ECC
 * controller to initialize/clear the entire memory data and ECC data.
 */
static int altr_init_memory_port(void __iomem *ioaddr)
{
	int limit = ALTR_A10_ECC_INIT_WATCHDOG_10US;

	ecc_set_bits(ALTR_A10_ECC_INITA, (ioaddr + ALTR_A10_ECC_CTRL_OFST));
	while (limit--) {
		if (ecc_test_bits(ALTR_A10_ECC_INITCOMPLETEA,
				  (ioaddr + ALTR_A10_ECC_INITSTAT_OFST)))
			break;
		udelay(1);
	}
	if (limit < 0)
		return -EBUSY;

	/* Clear any pending ECC interrupts */
	writel(ALTR_A10_ECC_ERRPENA_MASK,
	       (ioaddr + ALTR_A10_ECC_INTSTAT_OFST));

	return 0;
}

void socfpga_init_arria10_ocram_ecc(void)
{
	struct device_node *np;
	int ret = 0;
	void __iomem *ecc_block_base;

	if (!sys_manager_base_addr) {
		pr_err("SOCFPGA: sys-mgr is not initialized\n");
		return;
	}

	/* Find the OCRAM EDAC device tree node */
	np = of_find_compatible_node(NULL, NULL, "altr,socfpga-a10-ocram-ecc");
	if (!np) {
		pr_err("Unable to find socfpga-a10-ocram-ecc\n");
		return;
	}

	/* Map the ECC Block */
	ecc_block_base = of_iomap(np, 0);
	of_node_put(np);
	if (!ecc_block_base) {
		pr_err("Unable to map OCRAM ECC block\n");
		return;
	}

	/* Disable ECC */
	writel(ALTR_A10_OCRAM_ECC_EN_CTL,
	       sys_manager_base_addr + A10_SYSMGR_ECC_INTMASK_SET_OFST);
	ecc_clear_bits(ALTR_A10_ECC_SERRINTEN,
		       (ecc_block_base + ALTR_A10_ECC_ERRINTEN_OFST));
	ecc_clear_bits(ALTR_A10_OCRAM_ECC_EN_CTL,
		       (ecc_block_base + ALTR_A10_ECC_CTRL_OFST));

	/* Ensure all writes complete */
	wmb();

	/* Use HW initialization block to initialize memory for ECC */
	ret = altr_init_memory_port(ecc_block_base);
	if (ret) {
		pr_err("ECC: cannot init OCRAM PORTA memory\n");
		goto exit;
	}

	/* Enable ECC */
	ecc_set_bits(ALTR_A10_OCRAM_ECC_EN_CTL,
		     (ecc_block_base + ALTR_A10_ECC_CTRL_OFST));
	ecc_set_bits(ALTR_A10_ECC_SERRINTEN,
		     (ecc_block_base + ALTR_A10_ECC_ERRINTEN_OFST));
	writel(ALTR_A10_OCRAM_ECC_EN_CTL,
	       sys_manager_base_addr + A10_SYSMGR_ECC_INTMASK_CLR_OFST);

	/* Ensure all writes complete */
	wmb();
exit:
	iounmap(ecc_block_base);
}