summaryrefslogtreecommitdiffstats
path: root/drivers/mtd/nand/denali_spl.c
blob: c6930325307cdab94a78f8453f0fc54f7e2a64d3 (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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
/*
 * Copyright (C) 2014       Panasonic Corporation
 * Copyright (C) 2014-2015  Masahiro Yamada <yamada.masahiro@socionext.com>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <asm/io.h>
#include <asm/unaligned.h>
#include <linux/mtd/nand.h>
#include "denali.h"

#define SPARE_ACCESS		0x41
#define MAIN_ACCESS		0x42
#define PIPELINE_ACCESS		0x2000

#define BANK(x) ((x) << 24)

static void __iomem *denali_flash_mem =
			(void __iomem *)CONFIG_SYS_NAND_DATA_BASE;
static void __iomem *denali_flash_reg =
			(void __iomem *)CONFIG_SYS_NAND_REGS_BASE;

static const int flash_bank;
static int page_size, oob_size, pages_per_block;

static void index_addr(uint32_t address, uint32_t data)
{
	writel(address, denali_flash_mem + INDEX_CTRL_REG);
	writel(data, denali_flash_mem + INDEX_DATA_REG);
}

static int wait_for_irq(uint32_t irq_mask)
{
	unsigned long timeout = 1000000;
	uint32_t intr_status;

	do {
		intr_status = readl(denali_flash_reg + INTR_STATUS(flash_bank));

		if (intr_status & INTR_STATUS__ECC_UNCOR_ERR) {
			debug("Uncorrected ECC detected\n");
			return -EBADMSG;
		}

		if (intr_status & irq_mask)
			break;

		udelay(1);
		timeout--;
	} while (timeout);

	if (!timeout) {
		debug("Timeout with interrupt status %08x\n", intr_status);
		return -EIO;
	}

	return 0;
}

static void read_data_from_flash_mem(uint8_t *buf, int len)
{
	int i;
	uint32_t *buf32;

	/* transfer the data from the flash */
	buf32 = (uint32_t *)buf;

	/*
	 * Let's take care of unaligned access although it rarely happens.
	 * Avoid put_unaligned() for the normal use cases since it leads to
	 * a bit performance regression.
	 */
	if ((unsigned long)buf32 % 4) {
		for (i = 0; i < len / 4; i++)
			put_unaligned(readl(denali_flash_mem + INDEX_DATA_REG),
				      buf32++);
	} else {
		for (i = 0; i < len / 4; i++)
			*buf32++ = readl(denali_flash_mem + INDEX_DATA_REG);
	}

	if (len % 4) {
		u32 tmp;

		tmp = cpu_to_le32(readl(denali_flash_mem + INDEX_DATA_REG));
		buf = (uint8_t *)buf32;
		for (i = 0; i < len % 4; i++) {
			*buf++ = tmp;
			tmp >>= 8;
		}
	}
}

int denali_send_pipeline_cmd(int page, int ecc_en, int access_type)
{
	uint32_t addr, cmd;
	static uint32_t page_count = 1;

	writel(ecc_en, denali_flash_reg + ECC_ENABLE);

	/* clear all bits of intr_status. */
	writel(0xffff, denali_flash_reg + INTR_STATUS(flash_bank));

	addr = BANK(flash_bank) | page;

	/* setup the acccess type */
	cmd = MODE_10 | addr;
	index_addr(cmd, access_type);

	/* setup the pipeline command */
	index_addr(cmd, PIPELINE_ACCESS | page_count);

	cmd = MODE_01 | addr;
	writel(cmd, denali_flash_mem + INDEX_CTRL_REG);

	return wait_for_irq(INTR_STATUS__LOAD_COMP);
}

static int nand_read_oob(void *buf, int page)
{
	int ret;

	ret = denali_send_pipeline_cmd(page, 0, SPARE_ACCESS);
	if (ret < 0)
		return ret;

	read_data_from_flash_mem(buf, oob_size);

	return 0;
}

static int nand_read_page(void *buf, int page)
{
	int ret;

	ret = denali_send_pipeline_cmd(page, 1, MAIN_ACCESS);
	if (ret < 0)
		return ret;

	read_data_from_flash_mem(buf, page_size);

	return 0;
}

static int nand_block_isbad(void *buf, int block)
{
	int ret;

	ret = nand_read_oob(buf, block * pages_per_block);
	if (ret < 0)
		return ret;

	return *((uint8_t *)buf + CONFIG_SYS_NAND_BAD_BLOCK_POS) != 0xff;
}

/* nand_init() - initialize data to make nand usable by SPL */
void nand_init(void)
{
	/* access to main area */
	writel(0, denali_flash_reg + TRANSFER_SPARE_REG);

	/*
	 * These registers are expected to be already set by the hardware
	 * or earlier boot code.  So we read these values out.
	 */
	page_size = readl(denali_flash_reg + DEVICE_MAIN_AREA_SIZE);
	oob_size = readl(denali_flash_reg + DEVICE_SPARE_AREA_SIZE);
	pages_per_block = readl(denali_flash_reg + PAGES_PER_BLOCK);
}

int nand_spl_load_image(uint32_t offs, unsigned int size, void *dst)
{
	int block, page, column, readlen;
	int ret;
	int force_bad_block_check = 1;

	page = offs / page_size;
	column = offs % page_size;

	block = page / pages_per_block;
	page = page % pages_per_block;

	while (size) {
		if (force_bad_block_check || page == 0) {
			ret = nand_block_isbad(dst, block);
			if (ret < 0)
				return ret;

			if (ret) {
				block++;
				continue;
			}
		}

		force_bad_block_check = 0;

		ret = nand_read_page(dst, block * pages_per_block + page);
		if (ret < 0)
			return ret;

		readlen = min(page_size - column, (int)size);

		if (unlikely(column)) {
			/* Partial page read */
			memmove(dst, dst + column, readlen);
			column = 0;
		}

		size -= readlen;
		dst += readlen;
		page++;
		if (page == pages_per_block) {
			block++;
			page = 0;
		}
	}

	return 0;
}

void nand_deselect(void) {}
OpenPOWER on IntegriCloud