]>
Commit | Line | Data |
---|---|---|
845034e6 MY |
1 | /* |
2 | * Copyright (C) 2014 Panasonic Corporation | |
2bc1f2b5 | 3 | * Copyright (C) 2014-2015 Masahiro Yamada <yamada.masahiro@socionext.com> |
845034e6 MY |
4 | * |
5 | * SPDX-License-Identifier: GPL-2.0+ | |
6 | */ | |
7 | ||
8 | #include <common.h> | |
9 | #include <asm/io.h> | |
10 | #include <asm/unaligned.h> | |
11 | #include <linux/mtd/nand.h> | |
12 | #include "denali.h" | |
13 | ||
14 | #define SPARE_ACCESS 0x41 | |
15 | #define MAIN_ACCESS 0x42 | |
16 | #define PIPELINE_ACCESS 0x2000 | |
17 | ||
18 | #define BANK(x) ((x) << 24) | |
19 | ||
20 | static void __iomem *denali_flash_mem = | |
21 | (void __iomem *)CONFIG_SYS_NAND_DATA_BASE; | |
22 | static void __iomem *denali_flash_reg = | |
23 | (void __iomem *)CONFIG_SYS_NAND_REGS_BASE; | |
24 | ||
25 | static const int flash_bank; | |
845034e6 MY |
26 | static int page_size, oob_size, pages_per_block; |
27 | ||
28 | static void index_addr(uint32_t address, uint32_t data) | |
29 | { | |
30 | writel(address, denali_flash_mem + INDEX_CTRL_REG); | |
31 | writel(data, denali_flash_mem + INDEX_DATA_REG); | |
32 | } | |
33 | ||
34 | static int wait_for_irq(uint32_t irq_mask) | |
35 | { | |
36 | unsigned long timeout = 1000000; | |
37 | uint32_t intr_status; | |
38 | ||
39 | do { | |
40 | intr_status = readl(denali_flash_reg + INTR_STATUS(flash_bank)); | |
41 | ||
42 | if (intr_status & INTR_STATUS__ECC_UNCOR_ERR) { | |
43 | debug("Uncorrected ECC detected\n"); | |
ceee07b6 | 44 | return -EBADMSG; |
845034e6 MY |
45 | } |
46 | ||
47 | if (intr_status & irq_mask) | |
48 | break; | |
49 | ||
50 | udelay(1); | |
51 | timeout--; | |
52 | } while (timeout); | |
53 | ||
54 | if (!timeout) { | |
55 | debug("Timeout with interrupt status %08x\n", intr_status); | |
56 | return -EIO; | |
57 | } | |
58 | ||
59 | return 0; | |
60 | } | |
61 | ||
62 | static void read_data_from_flash_mem(uint8_t *buf, int len) | |
63 | { | |
64 | int i; | |
65 | uint32_t *buf32; | |
66 | ||
67 | /* transfer the data from the flash */ | |
68 | buf32 = (uint32_t *)buf; | |
69 | ||
70 | /* | |
71 | * Let's take care of unaligned access although it rarely happens. | |
72 | * Avoid put_unaligned() for the normal use cases since it leads to | |
73 | * a bit performance regression. | |
74 | */ | |
75 | if ((unsigned long)buf32 % 4) { | |
76 | for (i = 0; i < len / 4; i++) | |
77 | put_unaligned(readl(denali_flash_mem + INDEX_DATA_REG), | |
78 | buf32++); | |
79 | } else { | |
80 | for (i = 0; i < len / 4; i++) | |
81 | *buf32++ = readl(denali_flash_mem + INDEX_DATA_REG); | |
82 | } | |
83 | ||
84 | if (len % 4) { | |
85 | u32 tmp; | |
86 | ||
87 | tmp = cpu_to_le32(readl(denali_flash_mem + INDEX_DATA_REG)); | |
88 | buf = (uint8_t *)buf32; | |
89 | for (i = 0; i < len % 4; i++) { | |
90 | *buf++ = tmp; | |
91 | tmp >>= 8; | |
92 | } | |
93 | } | |
94 | } | |
95 | ||
96 | int denali_send_pipeline_cmd(int page, int ecc_en, int access_type) | |
97 | { | |
98 | uint32_t addr, cmd; | |
99 | static uint32_t page_count = 1; | |
100 | ||
101 | writel(ecc_en, denali_flash_reg + ECC_ENABLE); | |
102 | ||
103 | /* clear all bits of intr_status. */ | |
104 | writel(0xffff, denali_flash_reg + INTR_STATUS(flash_bank)); | |
105 | ||
106 | addr = BANK(flash_bank) | page; | |
107 | ||
108 | /* setup the acccess type */ | |
109 | cmd = MODE_10 | addr; | |
110 | index_addr(cmd, access_type); | |
111 | ||
112 | /* setup the pipeline command */ | |
113 | index_addr(cmd, PIPELINE_ACCESS | page_count); | |
114 | ||
115 | cmd = MODE_01 | addr; | |
116 | writel(cmd, denali_flash_mem + INDEX_CTRL_REG); | |
117 | ||
118 | return wait_for_irq(INTR_STATUS__LOAD_COMP); | |
119 | } | |
120 | ||
121 | static int nand_read_oob(void *buf, int page) | |
122 | { | |
123 | int ret; | |
124 | ||
125 | ret = denali_send_pipeline_cmd(page, 0, SPARE_ACCESS); | |
126 | if (ret < 0) | |
127 | return ret; | |
128 | ||
129 | read_data_from_flash_mem(buf, oob_size); | |
130 | ||
131 | return 0; | |
132 | } | |
133 | ||
134 | static int nand_read_page(void *buf, int page) | |
135 | { | |
136 | int ret; | |
137 | ||
138 | ret = denali_send_pipeline_cmd(page, 1, MAIN_ACCESS); | |
139 | if (ret < 0) | |
140 | return ret; | |
141 | ||
142 | read_data_from_flash_mem(buf, page_size); | |
143 | ||
144 | return 0; | |
145 | } | |
146 | ||
2bc1f2b5 | 147 | static int nand_block_isbad(void *buf, int block) |
845034e6 MY |
148 | { |
149 | int ret; | |
150 | ||
2bc1f2b5 | 151 | ret = nand_read_oob(buf, block * pages_per_block); |
845034e6 MY |
152 | if (ret < 0) |
153 | return ret; | |
154 | ||
2bc1f2b5 | 155 | return *((uint8_t *)buf + CONFIG_SYS_NAND_BAD_BLOCK_POS) != 0xff; |
845034e6 MY |
156 | } |
157 | ||
158 | /* nand_init() - initialize data to make nand usable by SPL */ | |
159 | void nand_init(void) | |
160 | { | |
161 | /* access to main area */ | |
162 | writel(0, denali_flash_reg + TRANSFER_SPARE_REG); | |
163 | ||
164 | /* | |
165 | * These registers are expected to be already set by the hardware | |
166 | * or earlier boot code. So we read these values out. | |
167 | */ | |
168 | page_size = readl(denali_flash_reg + DEVICE_MAIN_AREA_SIZE); | |
169 | oob_size = readl(denali_flash_reg + DEVICE_SPARE_AREA_SIZE); | |
170 | pages_per_block = readl(denali_flash_reg + PAGES_PER_BLOCK); | |
171 | } | |
172 | ||
173 | int nand_spl_load_image(uint32_t offs, unsigned int size, void *dst) | |
174 | { | |
175 | int block, page, column, readlen; | |
176 | int ret; | |
177 | int force_bad_block_check = 1; | |
178 | ||
179 | page = offs / page_size; | |
180 | column = offs % page_size; | |
181 | ||
182 | block = page / pages_per_block; | |
183 | page = page % pages_per_block; | |
184 | ||
185 | while (size) { | |
186 | if (force_bad_block_check || page == 0) { | |
2bc1f2b5 | 187 | ret = nand_block_isbad(dst, block); |
845034e6 MY |
188 | if (ret < 0) |
189 | return ret; | |
190 | ||
191 | if (ret) { | |
192 | block++; | |
193 | continue; | |
194 | } | |
195 | } | |
196 | ||
197 | force_bad_block_check = 0; | |
198 | ||
2bc1f2b5 MY |
199 | ret = nand_read_page(dst, block * pages_per_block + page); |
200 | if (ret < 0) | |
201 | return ret; | |
845034e6 | 202 | |
2bc1f2b5 | 203 | readlen = min(page_size - column, (int)size); |
845034e6 | 204 | |
2bc1f2b5 MY |
205 | if (unlikely(column)) { |
206 | /* Partial page read */ | |
207 | memmove(dst, dst + column, readlen); | |
845034e6 | 208 | column = 0; |
845034e6 MY |
209 | } |
210 | ||
211 | size -= readlen; | |
212 | dst += readlen; | |
213 | page++; | |
214 | if (page == pages_per_block) { | |
215 | block++; | |
216 | page = 0; | |
217 | } | |
218 | } | |
219 | ||
220 | return 0; | |
221 | } | |
222 | ||
223 | void nand_deselect(void) {} |