xref: /openbmc/u-boot/drivers/mtd/cfi_mtd.c (revision 236aad87)
1 /*
2  * (C) Copyright 2008 Semihalf
3  *
4  * Written by: Piotr Ziecik <kosmo@semihalf.com>
5  *
6  * See file CREDITS for list of people who contributed to this
7  * project.
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License as
11  * published by the Free Software Foundation; either version 2 of
12  * the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
22  * MA 02111-1307 USA
23  *
24  */
25 
26 #include <common.h>
27 #include <flash.h>
28 #include <malloc.h>
29 
30 #include <asm/errno.h>
31 #include <linux/mtd/mtd.h>
32 #include <linux/mtd/concat.h>
33 
34 extern flash_info_t flash_info[];
35 
36 static struct mtd_info cfi_mtd_info[CONFIG_SYS_MAX_FLASH_BANKS];
37 static char cfi_mtd_names[CONFIG_SYS_MAX_FLASH_BANKS][16];
38 #ifdef CONFIG_MTD_CONCAT
39 static char c_mtd_name[16];
40 #endif
41 
42 static int cfi_mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
43 {
44 	flash_info_t *fi = mtd->priv;
45 	size_t a_start = fi->start[0] + instr->addr;
46 	size_t a_end = a_start + instr->len;
47 	int s_first = -1;
48 	int s_last = -1;
49 	int error, sect;
50 
51 	for (sect = 0; sect < fi->sector_count; sect++) {
52 		if (a_start == fi->start[sect])
53 			s_first = sect;
54 
55 		if (sect < fi->sector_count - 1) {
56 			if (a_end == fi->start[sect + 1]) {
57 				s_last = sect;
58 				break;
59 			}
60 		} else {
61 			s_last = sect;
62 			break;
63 		}
64 	}
65 
66 	if (s_first >= 0 && s_first <= s_last) {
67 		instr->state = MTD_ERASING;
68 
69 		flash_set_verbose(0);
70 		error = flash_erase(fi, s_first, s_last);
71 		flash_set_verbose(1);
72 
73 		if (error) {
74 			instr->state = MTD_ERASE_FAILED;
75 			return -EIO;
76 		}
77 
78 		instr->state = MTD_ERASE_DONE;
79 		mtd_erase_callback(instr);
80 		return 0;
81 	}
82 
83 	return -EINVAL;
84 }
85 
86 static int cfi_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
87 	size_t *retlen, u_char *buf)
88 {
89 	flash_info_t *fi = mtd->priv;
90 	u_char *f = (u_char*)(fi->start[0]) + from;
91 
92 	memcpy(buf, f, len);
93 	*retlen = len;
94 
95 	return 0;
96 }
97 
98 static int cfi_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
99 	size_t *retlen, const u_char *buf)
100 {
101 	flash_info_t *fi = mtd->priv;
102 	u_long t = fi->start[0] + to;
103 	int error;
104 
105 	flash_set_verbose(0);
106 	error = write_buff(fi, (u_char*)buf, t, len);
107 	flash_set_verbose(1);
108 
109 	if (!error) {
110 		*retlen = len;
111 		return 0;
112 	}
113 
114 	return -EIO;
115 }
116 
117 static void cfi_mtd_sync(struct mtd_info *mtd)
118 {
119 	/*
120 	 * This function should wait until all pending operations
121 	 * finish. However this driver is fully synchronous, so
122 	 * this function returns immediately
123 	 */
124 }
125 
126 static int cfi_mtd_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
127 {
128 	flash_info_t *fi = mtd->priv;
129 
130 	flash_set_verbose(0);
131 	flash_protect(FLAG_PROTECT_SET, fi->start[0] + ofs,
132 					fi->start[0] + ofs + len - 1, fi);
133 	flash_set_verbose(1);
134 
135 	return 0;
136 }
137 
138 static int cfi_mtd_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
139 {
140 	flash_info_t *fi = mtd->priv;
141 
142 	flash_set_verbose(0);
143 	flash_protect(FLAG_PROTECT_CLEAR, fi->start[0] + ofs,
144 					fi->start[0] + ofs + len - 1, fi);
145 	flash_set_verbose(1);
146 
147 	return 0;
148 }
149 
150 static int cfi_mtd_set_erasesize(struct mtd_info *mtd, flash_info_t *fi)
151 {
152 	int sect_size = 0;
153 	int sect_size_old = 0;
154 	int sect;
155 	int regions = 0;
156 	int numblocks = 0;
157 	ulong offset = 0;
158 	ulong base_addr = fi->start[0];
159 
160 	/*
161 	 * First detect the number of eraseregions so that we can allocate
162 	 * the array of eraseregions correctly
163 	 */
164 	for (sect = 0; sect < fi->sector_count; sect++) {
165 		if (sect_size_old != flash_sector_size(fi, sect))
166 			regions++;
167 		sect_size_old = flash_sector_size(fi, sect);
168 	}
169 
170 	mtd->eraseregions = malloc(sizeof(struct mtd_erase_region_info) * regions);
171 
172 	/*
173 	 * Now detect the largest sector and fill the eraseregions
174 	 */
175 	sect_size_old = 0;
176 	regions = 0;
177 	for (sect = 0; sect < fi->sector_count; sect++) {
178 		if ((sect_size_old != flash_sector_size(fi, sect)) &&
179 		    (sect_size_old != 0)) {
180 			mtd->eraseregions[regions].offset = offset - base_addr;
181 			mtd->eraseregions[regions].erasesize = sect_size_old;
182 			mtd->eraseregions[regions].numblocks = numblocks;
183 
184 			/* Now start counting the next eraseregions */
185 			numblocks = 0;
186 			regions++;
187 		} else {
188 			numblocks++;
189 		}
190 
191 		if (sect_size_old != flash_sector_size(fi, sect))
192 			offset = fi->start[sect];
193 
194 		/*
195 		 * Select the largest sector size as erasesize (e.g. for UBI)
196 		 */
197 		if (flash_sector_size(fi, sect) > sect_size)
198 			sect_size = flash_sector_size(fi, sect);
199 
200 		sect_size_old = flash_sector_size(fi, sect);
201 	}
202 
203 	/*
204 	 * Set the last region
205 	 */
206 	mtd->eraseregions[regions].offset = offset - base_addr;
207 	mtd->eraseregions[regions].erasesize = sect_size_old;
208 	mtd->eraseregions[regions].numblocks = numblocks + 1;
209 
210 	if (regions)
211 		mtd->numeraseregions = regions + 1;
212 	else
213 		mtd->numeraseregions = 0;
214 
215 	mtd->erasesize = sect_size;
216 
217 	return 0;
218 }
219 
220 int cfi_mtd_init(void)
221 {
222 	struct mtd_info *mtd;
223 	flash_info_t *fi;
224 	int error, i;
225 	int devices_found = 0;
226 	struct mtd_info *mtd_list[CONFIG_SYS_MAX_FLASH_BANKS];
227 
228 	for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) {
229 		fi = &flash_info[i];
230 		mtd = &cfi_mtd_info[i];
231 
232 		memset(mtd, 0, sizeof(struct mtd_info));
233 
234 		error = cfi_mtd_set_erasesize(mtd, fi);
235 		if (error)
236 			continue;
237 
238 		sprintf(cfi_mtd_names[i], "nor%d", i);
239 		mtd->name		= cfi_mtd_names[i];
240 		mtd->type		= MTD_NORFLASH;
241 		mtd->flags		= MTD_CAP_NORFLASH;
242 		mtd->size		= fi->size;
243 		mtd->writesize		= 1;
244 
245 		mtd->erase		= cfi_mtd_erase;
246 		mtd->read		= cfi_mtd_read;
247 		mtd->write		= cfi_mtd_write;
248 		mtd->sync		= cfi_mtd_sync;
249 		mtd->lock		= cfi_mtd_lock;
250 		mtd->unlock		= cfi_mtd_unlock;
251 		mtd->priv		= fi;
252 
253 		if (add_mtd_device(mtd))
254 			return -ENOMEM;
255 
256 		mtd_list[devices_found++] = mtd;
257 	}
258 
259 #ifdef CONFIG_MTD_CONCAT
260 	if (devices_found > 1) {
261 		/*
262 		 * We detected multiple devices. Concatenate them together.
263 		 */
264 		sprintf(c_mtd_name, "nor%d", devices_found);
265 		mtd = mtd_concat_create(mtd_list, devices_found, c_mtd_name);
266 
267 		if (mtd == NULL)
268 			return -ENXIO;
269 
270 		if (add_mtd_device(mtd))
271 			return -ENOMEM;
272 	}
273 #endif /* CONFIG_MTD_CONCAT */
274 
275 	return 0;
276 }
277