xref: /openbmc/u-boot/drivers/mtd/cfi_mtd.c (revision 26ddff2d)
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 #include <mtd/cfi_flash.h>
34 
35 static struct mtd_info cfi_mtd_info[CFI_MAX_FLASH_BANKS];
36 static char cfi_mtd_names[CFI_MAX_FLASH_BANKS][16];
37 #ifdef CONFIG_MTD_CONCAT
38 static char c_mtd_name[16];
39 #endif
40 
41 static int cfi_mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
42 {
43 	flash_info_t *fi = mtd->priv;
44 	size_t a_start = fi->start[0] + instr->addr;
45 	size_t a_end = a_start + instr->len;
46 	int s_first = -1;
47 	int s_last = -1;
48 	int error, sect;
49 
50 	for (sect = 0; sect < fi->sector_count; sect++) {
51 		if (a_start == fi->start[sect])
52 			s_first = sect;
53 
54 		if (sect < fi->sector_count - 1) {
55 			if (a_end == fi->start[sect + 1]) {
56 				s_last = sect;
57 				break;
58 			}
59 		} else {
60 			s_last = sect;
61 			break;
62 		}
63 	}
64 
65 	if (s_first >= 0 && s_first <= s_last) {
66 		instr->state = MTD_ERASING;
67 
68 		flash_set_verbose(0);
69 		error = flash_erase(fi, s_first, s_last);
70 		flash_set_verbose(1);
71 
72 		if (error) {
73 			instr->state = MTD_ERASE_FAILED;
74 			return -EIO;
75 		}
76 
77 		instr->state = MTD_ERASE_DONE;
78 		mtd_erase_callback(instr);
79 		return 0;
80 	}
81 
82 	return -EINVAL;
83 }
84 
85 static int cfi_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
86 	size_t *retlen, u_char *buf)
87 {
88 	flash_info_t *fi = mtd->priv;
89 	u_char *f = (u_char*)(fi->start[0]) + from;
90 
91 	memcpy(buf, f, len);
92 	*retlen = len;
93 
94 	return 0;
95 }
96 
97 static int cfi_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
98 	size_t *retlen, const u_char *buf)
99 {
100 	flash_info_t *fi = mtd->priv;
101 	u_long t = fi->start[0] + to;
102 	int error;
103 
104 	flash_set_verbose(0);
105 	error = write_buff(fi, (u_char*)buf, t, len);
106 	flash_set_verbose(1);
107 
108 	if (!error) {
109 		*retlen = len;
110 		return 0;
111 	}
112 
113 	return -EIO;
114 }
115 
116 static void cfi_mtd_sync(struct mtd_info *mtd)
117 {
118 	/*
119 	 * This function should wait until all pending operations
120 	 * finish. However this driver is fully synchronous, so
121 	 * this function returns immediately
122 	 */
123 }
124 
125 static int cfi_mtd_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
126 {
127 	flash_info_t *fi = mtd->priv;
128 
129 	flash_set_verbose(0);
130 	flash_protect(FLAG_PROTECT_SET, fi->start[0] + ofs,
131 					fi->start[0] + ofs + len - 1, fi);
132 	flash_set_verbose(1);
133 
134 	return 0;
135 }
136 
137 static int cfi_mtd_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
138 {
139 	flash_info_t *fi = mtd->priv;
140 
141 	flash_set_verbose(0);
142 	flash_protect(FLAG_PROTECT_CLEAR, fi->start[0] + ofs,
143 					fi->start[0] + ofs + len - 1, fi);
144 	flash_set_verbose(1);
145 
146 	return 0;
147 }
148 
149 static int cfi_mtd_set_erasesize(struct mtd_info *mtd, flash_info_t *fi)
150 {
151 	int sect_size = 0;
152 	int sect_size_old = 0;
153 	int sect;
154 	int regions = 0;
155 	int numblocks = 0;
156 	ulong offset;
157 	ulong base_addr;
158 
159 	/*
160 	 * First detect the number of eraseregions so that we can allocate
161 	 * the array of eraseregions correctly
162 	 */
163 	for (sect = 0; sect < fi->sector_count; sect++) {
164 		if (sect_size_old != flash_sector_size(fi, sect))
165 			regions++;
166 		sect_size_old = flash_sector_size(fi, sect);
167 	}
168 
169 	switch (regions) {
170 	case 0:
171 		return 1;
172 	case 1:	/* flash has uniform erase size */
173 		mtd->numeraseregions = 0;
174 		mtd->erasesize = sect_size_old;
175 		return 0;
176 	}
177 
178 	mtd->numeraseregions = regions;
179 	mtd->eraseregions = malloc(sizeof(struct mtd_erase_region_info) * regions);
180 
181 	/*
182 	 * Now detect the largest sector and fill the eraseregions
183 	 */
184 	regions = 0;
185 	base_addr = offset = fi->start[0];
186 	sect_size_old = flash_sector_size(fi, 0);
187 	for (sect = 0; sect < fi->sector_count; sect++) {
188 		if (sect_size_old != flash_sector_size(fi, sect)) {
189 			mtd->eraseregions[regions].offset = offset - base_addr;
190 			mtd->eraseregions[regions].erasesize = sect_size_old;
191 			mtd->eraseregions[regions].numblocks = numblocks;
192 			/* Now start counting the next eraseregions */
193 			numblocks = 0;
194 			regions++;
195 			offset = fi->start[sect];
196 		}
197 		numblocks++;
198 
199 		/*
200 		 * Select the largest sector size as erasesize (e.g. for UBI)
201 		 */
202 		if (flash_sector_size(fi, sect) > sect_size)
203 			sect_size = flash_sector_size(fi, sect);
204 
205 		sect_size_old = flash_sector_size(fi, sect);
206 	}
207 
208 	/*
209 	 * Set the last region
210 	 */
211 	mtd->eraseregions[regions].offset = offset - base_addr;
212 	mtd->eraseregions[regions].erasesize = sect_size_old;
213 	mtd->eraseregions[regions].numblocks = numblocks;
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 #ifdef CONFIG_MTD_CONCAT
226 	int devices_found = 0;
227 	struct mtd_info *mtd_list[CONFIG_SYS_MAX_FLASH_BANKS];
228 #endif
229 
230 	for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) {
231 		fi = &flash_info[i];
232 		mtd = &cfi_mtd_info[i];
233 
234 		memset(mtd, 0, sizeof(struct mtd_info));
235 
236 		error = cfi_mtd_set_erasesize(mtd, fi);
237 		if (error)
238 			continue;
239 
240 		sprintf(cfi_mtd_names[i], "nor%d", i);
241 		mtd->name		= cfi_mtd_names[i];
242 		mtd->type		= MTD_NORFLASH;
243 		mtd->flags		= MTD_CAP_NORFLASH;
244 		mtd->size		= fi->size;
245 		mtd->writesize		= 1;
246 
247 		mtd->erase		= cfi_mtd_erase;
248 		mtd->read		= cfi_mtd_read;
249 		mtd->write		= cfi_mtd_write;
250 		mtd->sync		= cfi_mtd_sync;
251 		mtd->lock		= cfi_mtd_lock;
252 		mtd->unlock		= cfi_mtd_unlock;
253 		mtd->priv		= fi;
254 
255 		if (add_mtd_device(mtd))
256 			return -ENOMEM;
257 
258 #ifdef CONFIG_MTD_CONCAT
259 		mtd_list[devices_found++] = mtd;
260 #endif
261 	}
262 
263 #ifdef CONFIG_MTD_CONCAT
264 	if (devices_found > 1) {
265 		/*
266 		 * We detected multiple devices. Concatenate them together.
267 		 */
268 		sprintf(c_mtd_name, "nor%d", devices_found);
269 		mtd = mtd_concat_create(mtd_list, devices_found, c_mtd_name);
270 
271 		if (mtd == NULL)
272 			return -ENXIO;
273 
274 		if (add_mtd_device(mtd))
275 			return -ENOMEM;
276 	}
277 #endif /* CONFIG_MTD_CONCAT */
278 
279 	return 0;
280 }
281