xref: /openbmc/u-boot/drivers/mtd/cfi_mtd.c (revision ebce73f0)
1 /*
2  * (C) Copyright 2008 Semihalf
3  *
4  * Written by: Piotr Ziecik <kosmo@semihalf.com>
5  *
6  * SPDX-License-Identifier:	GPL-2.0+
7  */
8 
9 #include <common.h>
10 #include <flash.h>
11 #include <malloc.h>
12 
13 #include <linux/errno.h>
14 #include <linux/mtd/mtd.h>
15 #include <linux/mtd/concat.h>
16 #include <mtd/cfi_flash.h>
17 
18 static struct mtd_info cfi_mtd_info[CFI_MAX_FLASH_BANKS];
19 static char cfi_mtd_names[CFI_MAX_FLASH_BANKS][16];
20 #ifdef CONFIG_MTD_CONCAT
21 static char c_mtd_name[16];
22 #endif
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 
33 	for (sect = 0; sect < fi->sector_count; sect++) {
34 		if (a_start == fi->start[sect])
35 			s_first = sect;
36 
37 		if (sect < fi->sector_count - 1) {
38 			if (a_end == fi->start[sect + 1]) {
39 				s_last = sect;
40 				break;
41 			}
42 		} else {
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 
108 static int cfi_mtd_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
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 
120 static int cfi_mtd_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
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;
135 	int sect_size_old = 0;
136 	int sect;
137 	int regions = 0;
138 	int numblocks = 0;
139 	ulong offset;
140 	ulong base_addr;
141 
142 	/*
143 	 * First detect the number of eraseregions so that we can allocate
144 	 * the array of eraseregions correctly
145 	 */
146 	for (sect = 0; sect < fi->sector_count; sect++) {
147 		if (sect_size_old != flash_sector_size(fi, sect))
148 			regions++;
149 		sect_size_old = flash_sector_size(fi, sect);
150 	}
151 
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;
162 	mtd->eraseregions = malloc(sizeof(struct mtd_erase_region_info) * regions);
163 
164 	/*
165 	 * Now detect the largest sector and fill the eraseregions
166 	 */
167 	regions = 0;
168 	base_addr = offset = fi->start[0];
169 	sect_size_old = flash_sector_size(fi, 0);
170 	for (sect = 0; sect < fi->sector_count; sect++) {
171 		if (sect_size_old != flash_sector_size(fi, sect)) {
172 			mtd->eraseregions[regions].offset = offset - base_addr;
173 			mtd->eraseregions[regions].erasesize = sect_size_old;
174 			mtd->eraseregions[regions].numblocks = numblocks;
175 			/* Now start counting the next eraseregions */
176 			numblocks = 0;
177 			regions++;
178 			offset = fi->start[sect];
179 		}
180 		numblocks++;
181 
182 		/*
183 		 * Select the largest sector size as erasesize (e.g. for UBI)
184 		 */
185 		if (flash_sector_size(fi, sect) > sect_size)
186 			sect_size = flash_sector_size(fi, sect);
187 
188 		sect_size_old = flash_sector_size(fi, sect);
189 	}
190 
191 	/*
192 	 * Set the last region
193 	 */
194 	mtd->eraseregions[regions].offset = offset - base_addr;
195 	mtd->eraseregions[regions].erasesize = sect_size_old;
196 	mtd->eraseregions[regions].numblocks = numblocks;
197 
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;
208 #ifdef CONFIG_MTD_CONCAT
209 	int devices_found = 0;
210 	struct mtd_info *mtd_list[CONFIG_SYS_MAX_FLASH_BANKS];
211 #endif
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 
223 		sprintf(cfi_mtd_names[i], "nor%d", i);
224 		mtd->name		= cfi_mtd_names[i];
225 		mtd->type		= MTD_NORFLASH;
226 		mtd->flags		= MTD_CAP_NORFLASH;
227 		mtd->size		= fi->size;
228 		mtd->writesize		= 1;
229 		mtd->writebufsize	= mtd->writesize;
230 
231 		mtd->_erase		= cfi_mtd_erase;
232 		mtd->_read		= cfi_mtd_read;
233 		mtd->_write		= cfi_mtd_write;
234 		mtd->_sync		= cfi_mtd_sync;
235 		mtd->_lock		= cfi_mtd_lock;
236 		mtd->_unlock		= cfi_mtd_unlock;
237 		mtd->priv		= fi;
238 
239 		if (add_mtd_device(mtd))
240 			return -ENOMEM;
241 
242 #ifdef CONFIG_MTD_CONCAT
243 		mtd_list[devices_found++] = mtd;
244 #endif
245 	}
246 
247 #ifdef CONFIG_MTD_CONCAT
248 	if (devices_found > 1) {
249 		/*
250 		 * We detected multiple devices. Concatenate them together.
251 		 */
252 		sprintf(c_mtd_name, "nor%d", devices_found);
253 		mtd = mtd_concat_create(mtd_list, devices_found, c_mtd_name);
254 
255 		if (mtd == NULL)
256 			return -ENXIO;
257 
258 		if (add_mtd_device(mtd))
259 			return -ENOMEM;
260 	}
261 #endif /* CONFIG_MTD_CONCAT */
262 
263 	return 0;
264 }
265