]>
Commit | Line | Data |
---|---|---|
91809ed5 PZ |
1 | /* |
2 | * (C) Copyright 2008 Semihalf | |
3 | * | |
4 | * Written by: Piotr Ziecik <kosmo@semihalf.com> | |
5 | * | |
1a459660 | 6 | * SPDX-License-Identifier: GPL-2.0+ |
91809ed5 PZ |
7 | */ |
8 | ||
9 | #include <common.h> | |
10 | #include <flash.h> | |
0a572655 | 11 | #include <malloc.h> |
91809ed5 PZ |
12 | |
13 | #include <asm/errno.h> | |
14 | #include <linux/mtd/mtd.h> | |
0a572655 | 15 | #include <linux/mtd/concat.h> |
3c29975e | 16 | #include <mtd/cfi_flash.h> |
91809ed5 | 17 | |
9578718c KP |
18 | static struct mtd_info cfi_mtd_info[CFI_MAX_FLASH_BANKS]; |
19 | static char cfi_mtd_names[CFI_MAX_FLASH_BANKS][16]; | |
0a572655 SR |
20 | #ifdef CONFIG_MTD_CONCAT |
21 | static char c_mtd_name[16]; | |
22 | #endif | |
91809ed5 PZ |
23 | |
24 | static int cfi_mtd_erase(struct mtd_info *mtd, struct erase_info *instr) | |
25 | { | |
26 | flash_info_t *fi = mtd->priv; | |
27 | size_t a_start = fi->start[0] + instr->addr; | |
28 | size_t a_end = a_start + instr->len; | |
29 | int s_first = -1; | |
30 | int s_last = -1; | |
31 | int error, sect; | |
32 | ||
dba6fcf6 | 33 | for (sect = 0; sect < fi->sector_count; sect++) { |
91809ed5 PZ |
34 | if (a_start == fi->start[sect]) |
35 | s_first = sect; | |
36 | ||
dba6fcf6 SR |
37 | if (sect < fi->sector_count - 1) { |
38 | if (a_end == fi->start[sect + 1]) { | |
39 | s_last = sect; | |
40 | break; | |
41 | } | |
42 | } else { | |
91809ed5 PZ |
43 | s_last = sect; |
44 | break; | |
45 | } | |
46 | } | |
47 | ||
48 | if (s_first >= 0 && s_first <= s_last) { | |
49 | instr->state = MTD_ERASING; | |
50 | ||
51 | flash_set_verbose(0); | |
52 | error = flash_erase(fi, s_first, s_last); | |
53 | flash_set_verbose(1); | |
54 | ||
55 | if (error) { | |
56 | instr->state = MTD_ERASE_FAILED; | |
57 | return -EIO; | |
58 | } | |
59 | ||
60 | instr->state = MTD_ERASE_DONE; | |
61 | mtd_erase_callback(instr); | |
62 | return 0; | |
63 | } | |
64 | ||
65 | return -EINVAL; | |
66 | } | |
67 | ||
68 | static int cfi_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, | |
69 | size_t *retlen, u_char *buf) | |
70 | { | |
71 | flash_info_t *fi = mtd->priv; | |
72 | u_char *f = (u_char*)(fi->start[0]) + from; | |
73 | ||
74 | memcpy(buf, f, len); | |
75 | *retlen = len; | |
76 | ||
77 | return 0; | |
78 | } | |
79 | ||
80 | static int cfi_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, | |
81 | size_t *retlen, const u_char *buf) | |
82 | { | |
83 | flash_info_t *fi = mtd->priv; | |
84 | u_long t = fi->start[0] + to; | |
85 | int error; | |
86 | ||
87 | flash_set_verbose(0); | |
88 | error = write_buff(fi, (u_char*)buf, t, len); | |
89 | flash_set_verbose(1); | |
90 | ||
91 | if (!error) { | |
92 | *retlen = len; | |
93 | return 0; | |
94 | } | |
95 | ||
96 | return -EIO; | |
97 | } | |
98 | ||
99 | static void cfi_mtd_sync(struct mtd_info *mtd) | |
100 | { | |
101 | /* | |
102 | * This function should wait until all pending operations | |
103 | * finish. However this driver is fully synchronous, so | |
104 | * this function returns immediately | |
105 | */ | |
106 | } | |
107 | ||
8d2effea | 108 | static int cfi_mtd_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) |
91809ed5 PZ |
109 | { |
110 | flash_info_t *fi = mtd->priv; | |
111 | ||
112 | flash_set_verbose(0); | |
113 | flash_protect(FLAG_PROTECT_SET, fi->start[0] + ofs, | |
114 | fi->start[0] + ofs + len - 1, fi); | |
115 | flash_set_verbose(1); | |
116 | ||
117 | return 0; | |
118 | } | |
119 | ||
8d2effea | 120 | static int cfi_mtd_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) |
91809ed5 PZ |
121 | { |
122 | flash_info_t *fi = mtd->priv; | |
123 | ||
124 | flash_set_verbose(0); | |
125 | flash_protect(FLAG_PROTECT_CLEAR, fi->start[0] + ofs, | |
126 | fi->start[0] + ofs + len - 1, fi); | |
127 | flash_set_verbose(1); | |
128 | ||
129 | return 0; | |
130 | } | |
131 | ||
132 | static int cfi_mtd_set_erasesize(struct mtd_info *mtd, flash_info_t *fi) | |
133 | { | |
134 | int sect_size = 0; | |
0a572655 | 135 | int sect_size_old = 0; |
91809ed5 | 136 | int sect; |
0a572655 SR |
137 | int regions = 0; |
138 | int numblocks = 0; | |
f3dec798 LM |
139 | ulong offset; |
140 | ulong base_addr; | |
91809ed5 | 141 | |
f8e2b310 | 142 | /* |
0a572655 SR |
143 | * First detect the number of eraseregions so that we can allocate |
144 | * the array of eraseregions correctly | |
f8e2b310 | 145 | */ |
91809ed5 | 146 | for (sect = 0; sect < fi->sector_count; sect++) { |
0a572655 SR |
147 | if (sect_size_old != flash_sector_size(fi, sect)) |
148 | regions++; | |
149 | sect_size_old = flash_sector_size(fi, sect); | |
150 | } | |
151 | ||
f3dec798 LM |
152 | switch (regions) { |
153 | case 0: | |
154 | return 1; | |
155 | case 1: /* flash has uniform erase size */ | |
156 | mtd->numeraseregions = 0; | |
157 | mtd->erasesize = sect_size_old; | |
158 | return 0; | |
159 | } | |
160 | ||
161 | mtd->numeraseregions = regions; | |
0a572655 SR |
162 | mtd->eraseregions = malloc(sizeof(struct mtd_erase_region_info) * regions); |
163 | ||
164 | /* | |
165 | * Now detect the largest sector and fill the eraseregions | |
166 | */ | |
0a572655 | 167 | regions = 0; |
f3dec798 LM |
168 | base_addr = offset = fi->start[0]; |
169 | sect_size_old = flash_sector_size(fi, 0); | |
0a572655 | 170 | for (sect = 0; sect < fi->sector_count; sect++) { |
f3dec798 | 171 | if (sect_size_old != flash_sector_size(fi, sect)) { |
0a572655 SR |
172 | mtd->eraseregions[regions].offset = offset - base_addr; |
173 | mtd->eraseregions[regions].erasesize = sect_size_old; | |
174 | mtd->eraseregions[regions].numblocks = numblocks; | |
0a572655 SR |
175 | /* Now start counting the next eraseregions */ |
176 | numblocks = 0; | |
177 | regions++; | |
0a572655 | 178 | offset = fi->start[sect]; |
f3dec798 LM |
179 | } |
180 | numblocks++; | |
0a572655 SR |
181 | |
182 | /* | |
183 | * Select the largest sector size as erasesize (e.g. for UBI) | |
184 | */ | |
f8e2b310 | 185 | if (flash_sector_size(fi, sect) > sect_size) |
91809ed5 | 186 | sect_size = flash_sector_size(fi, sect); |
0a572655 SR |
187 | |
188 | sect_size_old = flash_sector_size(fi, sect); | |
91809ed5 PZ |
189 | } |
190 | ||
0a572655 SR |
191 | /* |
192 | * Set the last region | |
193 | */ | |
194 | mtd->eraseregions[regions].offset = offset - base_addr; | |
195 | mtd->eraseregions[regions].erasesize = sect_size_old; | |
f3dec798 | 196 | mtd->eraseregions[regions].numblocks = numblocks; |
0a572655 | 197 | |
91809ed5 PZ |
198 | mtd->erasesize = sect_size; |
199 | ||
200 | return 0; | |
201 | } | |
202 | ||
203 | int cfi_mtd_init(void) | |
204 | { | |
205 | struct mtd_info *mtd; | |
206 | flash_info_t *fi; | |
207 | int error, i; | |
419a1fe9 | 208 | #ifdef CONFIG_MTD_CONCAT |
0a572655 SR |
209 | int devices_found = 0; |
210 | struct mtd_info *mtd_list[CONFIG_SYS_MAX_FLASH_BANKS]; | |
419a1fe9 | 211 | #endif |
91809ed5 PZ |
212 | |
213 | for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) { | |
214 | fi = &flash_info[i]; | |
215 | mtd = &cfi_mtd_info[i]; | |
216 | ||
217 | memset(mtd, 0, sizeof(struct mtd_info)); | |
218 | ||
219 | error = cfi_mtd_set_erasesize(mtd, fi); | |
220 | if (error) | |
221 | continue; | |
222 | ||
c203ef5d AH |
223 | sprintf(cfi_mtd_names[i], "nor%d", i); |
224 | mtd->name = cfi_mtd_names[i]; | |
91809ed5 PZ |
225 | mtd->type = MTD_NORFLASH; |
226 | mtd->flags = MTD_CAP_NORFLASH; | |
227 | mtd->size = fi->size; | |
228 | mtd->writesize = 1; | |
229 | ||
dfe64e2c SL |
230 | mtd->_erase = cfi_mtd_erase; |
231 | mtd->_read = cfi_mtd_read; | |
232 | mtd->_write = cfi_mtd_write; | |
233 | mtd->_sync = cfi_mtd_sync; | |
234 | mtd->_lock = cfi_mtd_lock; | |
235 | mtd->_unlock = cfi_mtd_unlock; | |
91809ed5 PZ |
236 | mtd->priv = fi; |
237 | ||
238 | if (add_mtd_device(mtd)) | |
239 | return -ENOMEM; | |
0a572655 | 240 | |
419a1fe9 | 241 | #ifdef CONFIG_MTD_CONCAT |
0a572655 | 242 | mtd_list[devices_found++] = mtd; |
419a1fe9 | 243 | #endif |
0a572655 SR |
244 | } |
245 | ||
246 | #ifdef CONFIG_MTD_CONCAT | |
247 | if (devices_found > 1) { | |
248 | /* | |
249 | * We detected multiple devices. Concatenate them together. | |
250 | */ | |
251 | sprintf(c_mtd_name, "nor%d", devices_found); | |
252 | mtd = mtd_concat_create(mtd_list, devices_found, c_mtd_name); | |
253 | ||
254 | if (mtd == NULL) | |
255 | return -ENXIO; | |
256 | ||
257 | if (add_mtd_device(mtd)) | |
258 | return -ENOMEM; | |
91809ed5 | 259 | } |
0a572655 | 260 | #endif /* CONFIG_MTD_CONCAT */ |
91809ed5 PZ |
261 | |
262 | return 0; | |
263 | } |