]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * Common Flash Interface support: | |
25985edc | 3 | * Generic utility functions not dependent on command set |
1da177e4 LT |
4 | * |
5 | * Copyright (C) 2002 Red Hat | |
6 | * Copyright (C) 2003 STMicroelectronics Limited | |
7 | * | |
8 | * This code is covered by the GPL. | |
1da177e4 LT |
9 | */ |
10 | ||
11 | #include <linux/module.h> | |
12 | #include <linux/types.h> | |
13 | #include <linux/kernel.h> | |
1da177e4 LT |
14 | #include <asm/io.h> |
15 | #include <asm/byteorder.h> | |
16 | ||
17 | #include <linux/errno.h> | |
18 | #include <linux/slab.h> | |
19 | #include <linux/delay.h> | |
20 | #include <linux/interrupt.h> | |
21 | #include <linux/mtd/xip.h> | |
22 | #include <linux/mtd/mtd.h> | |
23 | #include <linux/mtd/map.h> | |
24 | #include <linux/mtd/cfi.h> | |
1da177e4 | 25 | |
c314dfdc DW |
26 | int __xipram cfi_qry_present(struct map_info *map, __u32 base, |
27 | struct cfi_private *cfi) | |
2e489e07 AK |
28 | { |
29 | int osf = cfi->interleave * cfi->device_type; /* scale factor */ | |
30 | map_word val[3]; | |
31 | map_word qry[3]; | |
32 | ||
33 | qry[0] = cfi_build_cmd('Q', map, cfi); | |
34 | qry[1] = cfi_build_cmd('R', map, cfi); | |
35 | qry[2] = cfi_build_cmd('Y', map, cfi); | |
36 | ||
37 | val[0] = map_read(map, base + osf*0x10); | |
38 | val[1] = map_read(map, base + osf*0x11); | |
39 | val[2] = map_read(map, base + osf*0x12); | |
40 | ||
41 | if (!map_word_equal(map, qry[0], val[0])) | |
42 | return 0; | |
43 | ||
44 | if (!map_word_equal(map, qry[1], val[1])) | |
45 | return 0; | |
46 | ||
47 | if (!map_word_equal(map, qry[2], val[2])) | |
48 | return 0; | |
49 | ||
50 | return 1; /* "QRY" found */ | |
51 | } | |
c314dfdc | 52 | EXPORT_SYMBOL_GPL(cfi_qry_present); |
2e489e07 | 53 | |
c314dfdc DW |
54 | int __xipram cfi_qry_mode_on(uint32_t base, struct map_info *map, |
55 | struct cfi_private *cfi) | |
2e489e07 AK |
56 | { |
57 | cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); | |
58 | cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); | |
c314dfdc | 59 | if (cfi_qry_present(map, base, cfi)) |
2e489e07 AK |
60 | return 1; |
61 | /* QRY not found probably we deal with some odd CFI chips */ | |
62 | /* Some revisions of some old Intel chips? */ | |
63 | cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); | |
64 | cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); | |
65 | cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); | |
c314dfdc | 66 | if (cfi_qry_present(map, base, cfi)) |
2e489e07 AK |
67 | return 1; |
68 | /* ST M29DW chips */ | |
69 | cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); | |
70 | cfi_send_gen_cmd(0x98, 0x555, base, map, cfi, cfi->device_type, NULL); | |
4a589486 GL |
71 | if (cfi_qry_present(map, base, cfi)) |
72 | return 1; | |
73 | /* some old SST chips, e.g. 39VF160x/39VF320x */ | |
74 | cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); | |
75 | cfi_send_gen_cmd(0xAA, 0x5555, base, map, cfi, cfi->device_type, NULL); | |
76 | cfi_send_gen_cmd(0x55, 0x2AAA, base, map, cfi, cfi->device_type, NULL); | |
77 | cfi_send_gen_cmd(0x98, 0x5555, base, map, cfi, cfi->device_type, NULL); | |
fc61015f GL |
78 | if (cfi_qry_present(map, base, cfi)) |
79 | return 1; | |
80 | /* SST 39VF640xB */ | |
81 | cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); | |
82 | cfi_send_gen_cmd(0xAA, 0x555, base, map, cfi, cfi->device_type, NULL); | |
83 | cfi_send_gen_cmd(0x55, 0x2AA, base, map, cfi, cfi->device_type, NULL); | |
84 | cfi_send_gen_cmd(0x98, 0x555, base, map, cfi, cfi->device_type, NULL); | |
c314dfdc | 85 | if (cfi_qry_present(map, base, cfi)) |
2e489e07 AK |
86 | return 1; |
87 | /* QRY not found */ | |
88 | return 0; | |
89 | } | |
c314dfdc DW |
90 | EXPORT_SYMBOL_GPL(cfi_qry_mode_on); |
91 | ||
92 | void __xipram cfi_qry_mode_off(uint32_t base, struct map_info *map, | |
93 | struct cfi_private *cfi) | |
2e489e07 AK |
94 | { |
95 | cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); | |
96 | cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); | |
23af51ec MC |
97 | /* M29W128G flashes require an additional reset command |
98 | when exit qry mode */ | |
99 | if ((cfi->mfr == CFI_MFR_ST) && (cfi->id == 0x227E || cfi->id == 0x7E)) | |
100 | cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); | |
2e489e07 | 101 | } |
c314dfdc | 102 | EXPORT_SYMBOL_GPL(cfi_qry_mode_off); |
2e489e07 | 103 | |
1da177e4 LT |
104 | struct cfi_extquery * |
105 | __xipram cfi_read_pri(struct map_info *map, __u16 adr, __u16 size, const char* name) | |
106 | { | |
107 | struct cfi_private *cfi = map->fldrv_priv; | |
108 | __u32 base = 0; // cfi->chips[0].start; | |
109 | int ofs_factor = cfi->interleave * cfi->device_type; | |
110 | int i; | |
111 | struct cfi_extquery *extp = NULL; | |
112 | ||
1da177e4 LT |
113 | if (!adr) |
114 | goto out; | |
115 | ||
087444da GL |
116 | printk(KERN_INFO "%s Extended Query Table at 0x%4.4X\n", name, adr); |
117 | ||
1da177e4 LT |
118 | extp = kmalloc(size, GFP_KERNEL); |
119 | if (!extp) { | |
120 | printk(KERN_ERR "Failed to allocate memory\n"); | |
121 | goto out; | |
122 | } | |
123 | ||
124 | #ifdef CONFIG_MTD_XIP | |
125 | local_irq_disable(); | |
126 | #endif | |
127 | ||
128 | /* Switch it into Query Mode */ | |
c314dfdc | 129 | cfi_qry_mode_on(base, map, cfi); |
1da177e4 LT |
130 | /* Read in the Extended Query Table */ |
131 | for (i=0; i<size; i++) { | |
1f948b43 | 132 | ((unsigned char *)extp)[i] = |
1da177e4 LT |
133 | cfi_read_query(map, base+((adr+i)*ofs_factor)); |
134 | } | |
135 | ||
136 | /* Make sure it returns to read mode */ | |
c314dfdc | 137 | cfi_qry_mode_off(base, map, cfi); |
1da177e4 LT |
138 | |
139 | #ifdef CONFIG_MTD_XIP | |
140 | (void) map_read(map, base); | |
ca5c23c3 | 141 | xip_iprefetch(); |
1da177e4 LT |
142 | local_irq_enable(); |
143 | #endif | |
144 | ||
1da177e4 LT |
145 | out: return extp; |
146 | } | |
147 | ||
148 | EXPORT_SYMBOL(cfi_read_pri); | |
149 | ||
150 | void cfi_fixup(struct mtd_info *mtd, struct cfi_fixup *fixups) | |
151 | { | |
152 | struct map_info *map = mtd->priv; | |
153 | struct cfi_private *cfi = map->fldrv_priv; | |
154 | struct cfi_fixup *f; | |
155 | ||
156 | for (f=fixups; f->fixup; f++) { | |
157 | if (((f->mfr == CFI_MFR_ANY) || (f->mfr == cfi->mfr)) && | |
158 | ((f->id == CFI_ID_ANY) || (f->id == cfi->id))) { | |
cc318222 | 159 | f->fixup(mtd); |
1da177e4 LT |
160 | } |
161 | } | |
162 | } | |
163 | ||
164 | EXPORT_SYMBOL(cfi_fixup); | |
165 | ||
166 | int cfi_varsize_frob(struct mtd_info *mtd, varsize_frob_t frob, | |
167 | loff_t ofs, size_t len, void *thunk) | |
168 | { | |
169 | struct map_info *map = mtd->priv; | |
170 | struct cfi_private *cfi = map->fldrv_priv; | |
171 | unsigned long adr; | |
172 | int chipnum, ret = 0; | |
173 | int i, first; | |
174 | struct mtd_erase_region_info *regions = mtd->eraseregions; | |
175 | ||
176 | if (ofs > mtd->size) | |
177 | return -EINVAL; | |
178 | ||
179 | if ((len + ofs) > mtd->size) | |
180 | return -EINVAL; | |
181 | ||
182 | /* Check that both start and end of the requested erase are | |
183 | * aligned with the erasesize at the appropriate addresses. | |
184 | */ | |
185 | ||
186 | i = 0; | |
187 | ||
1f948b43 | 188 | /* Skip all erase regions which are ended before the start of |
1da177e4 LT |
189 | the requested erase. Actually, to save on the calculations, |
190 | we skip to the first erase region which starts after the | |
191 | start of the requested erase, and then go back one. | |
192 | */ | |
1f948b43 | 193 | |
1da177e4 LT |
194 | while (i < mtd->numeraseregions && ofs >= regions[i].offset) |
195 | i++; | |
196 | i--; | |
197 | ||
1f948b43 | 198 | /* OK, now i is pointing at the erase region in which this |
1da177e4 LT |
199 | erase request starts. Check the start of the requested |
200 | erase range is aligned with the erase size which is in | |
201 | effect here. | |
202 | */ | |
203 | ||
204 | if (ofs & (regions[i].erasesize-1)) | |
205 | return -EINVAL; | |
206 | ||
207 | /* Remember the erase region we start on */ | |
208 | first = i; | |
209 | ||
210 | /* Next, check that the end of the requested erase is aligned | |
211 | * with the erase region at that address. | |
212 | */ | |
213 | ||
214 | while (i<mtd->numeraseregions && (ofs + len) >= regions[i].offset) | |
215 | i++; | |
216 | ||
217 | /* As before, drop back one to point at the region in which | |
218 | the address actually falls | |
219 | */ | |
220 | i--; | |
1f948b43 | 221 | |
1da177e4 LT |
222 | if ((ofs + len) & (regions[i].erasesize-1)) |
223 | return -EINVAL; | |
224 | ||
225 | chipnum = ofs >> cfi->chipshift; | |
226 | adr = ofs - (chipnum << cfi->chipshift); | |
227 | ||
228 | i=first; | |
229 | ||
230 | while(len) { | |
231 | int size = regions[i].erasesize; | |
232 | ||
233 | ret = (*frob)(map, &cfi->chips[chipnum], adr, size, thunk); | |
1f948b43 | 234 | |
1da177e4 LT |
235 | if (ret) |
236 | return ret; | |
237 | ||
238 | adr += size; | |
239 | ofs += size; | |
240 | len -= size; | |
241 | ||
242 | if (ofs == regions[i].offset + size * regions[i].numblocks) | |
243 | i++; | |
244 | ||
245 | if (adr >> cfi->chipshift) { | |
246 | adr = 0; | |
247 | chipnum++; | |
1f948b43 | 248 | |
1da177e4 LT |
249 | if (chipnum >= cfi->numchips) |
250 | break; | |
251 | } | |
252 | } | |
253 | ||
254 | return 0; | |
255 | } | |
256 | ||
257 | EXPORT_SYMBOL(cfi_varsize_frob); | |
258 | ||
259 | MODULE_LICENSE("GPL"); |