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