blob: c5a84fda54107f32ebb5e00d3ae457914a958aa4 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * Common Flash Interface support:
3 * Generic utility functions not dependant on command set
4 *
5 * Copyright (C) 2002 Red Hat
6 * Copyright (C) 2003 STMicroelectronics Limited
7 *
8 * This code is covered by the GPL.
Linus Torvalds1da177e2005-04-16 15:20:36 -07009 */
10
11#include <linux/module.h>
12#include <linux/types.h>
13#include <linux/kernel.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070014#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>
25#include <linux/mtd/compatmac.h>
26
David Woodhousec314dfd2008-08-07 11:55:07 +010027int __xipram cfi_qry_present(struct map_info *map, __u32 base,
28 struct cfi_private *cfi)
Alexey Korolev2e489e02008-08-05 16:39:42 +010029{
30 int osf = cfi->interleave * cfi->device_type; /* scale factor */
31 map_word val[3];
32 map_word qry[3];
33
34 qry[0] = cfi_build_cmd('Q', map, cfi);
35 qry[1] = cfi_build_cmd('R', map, cfi);
36 qry[2] = cfi_build_cmd('Y', map, cfi);
37
38 val[0] = map_read(map, base + osf*0x10);
39 val[1] = map_read(map, base + osf*0x11);
40 val[2] = map_read(map, base + osf*0x12);
41
42 if (!map_word_equal(map, qry[0], val[0]))
43 return 0;
44
45 if (!map_word_equal(map, qry[1], val[1]))
46 return 0;
47
48 if (!map_word_equal(map, qry[2], val[2]))
49 return 0;
50
51 return 1; /* "QRY" found */
52}
David Woodhousec314dfd2008-08-07 11:55:07 +010053EXPORT_SYMBOL_GPL(cfi_qry_present);
Alexey Korolev2e489e02008-08-05 16:39:42 +010054
David Woodhousec314dfd2008-08-07 11:55:07 +010055int __xipram cfi_qry_mode_on(uint32_t base, struct map_info *map,
56 struct cfi_private *cfi)
Alexey Korolev2e489e02008-08-05 16:39:42 +010057{
58 cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL);
59 cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL);
David Woodhousec314dfd2008-08-07 11:55:07 +010060 if (cfi_qry_present(map, base, cfi))
Alexey Korolev2e489e02008-08-05 16:39:42 +010061 return 1;
62 /* QRY not found probably we deal with some odd CFI chips */
63 /* Some revisions of some old Intel chips? */
64 cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL);
65 cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL);
66 cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL);
David Woodhousec314dfd2008-08-07 11:55:07 +010067 if (cfi_qry_present(map, base, cfi))
Alexey Korolev2e489e02008-08-05 16:39:42 +010068 return 1;
69 /* ST M29DW chips */
70 cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL);
71 cfi_send_gen_cmd(0x98, 0x555, base, map, cfi, cfi->device_type, NULL);
David Woodhousec314dfd2008-08-07 11:55:07 +010072 if (cfi_qry_present(map, base, cfi))
Alexey Korolev2e489e02008-08-05 16:39:42 +010073 return 1;
74 /* QRY not found */
75 return 0;
76}
David Woodhousec314dfd2008-08-07 11:55:07 +010077EXPORT_SYMBOL_GPL(cfi_qry_mode_on);
78
79void __xipram cfi_qry_mode_off(uint32_t base, struct map_info *map,
80 struct cfi_private *cfi)
Alexey Korolev2e489e02008-08-05 16:39:42 +010081{
82 cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL);
83 cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL);
Massimo Cirillo23af51e2009-09-03 16:34:39 +020084 /* M29W128G flashes require an additional reset command
85 when exit qry mode */
86 if ((cfi->mfr == CFI_MFR_ST) && (cfi->id == 0x227E || cfi->id == 0x7E))
87 cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL);
Alexey Korolev2e489e02008-08-05 16:39:42 +010088}
David Woodhousec314dfd2008-08-07 11:55:07 +010089EXPORT_SYMBOL_GPL(cfi_qry_mode_off);
Alexey Korolev2e489e02008-08-05 16:39:42 +010090
Linus Torvalds1da177e2005-04-16 15:20:36 -070091struct cfi_extquery *
92__xipram cfi_read_pri(struct map_info *map, __u16 adr, __u16 size, const char* name)
93{
94 struct cfi_private *cfi = map->fldrv_priv;
95 __u32 base = 0; // cfi->chips[0].start;
96 int ofs_factor = cfi->interleave * cfi->device_type;
97 int i;
98 struct cfi_extquery *extp = NULL;
99
100 printk(" %s Extended Query Table at 0x%4.4X\n", name, adr);
101 if (!adr)
102 goto out;
103
104 extp = kmalloc(size, GFP_KERNEL);
105 if (!extp) {
106 printk(KERN_ERR "Failed to allocate memory\n");
107 goto out;
108 }
109
110#ifdef CONFIG_MTD_XIP
111 local_irq_disable();
112#endif
113
114 /* Switch it into Query Mode */
David Woodhousec314dfd2008-08-07 11:55:07 +0100115 cfi_qry_mode_on(base, map, cfi);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700116 /* Read in the Extended Query Table */
117 for (i=0; i<size; i++) {
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000118 ((unsigned char *)extp)[i] =
Linus Torvalds1da177e2005-04-16 15:20:36 -0700119 cfi_read_query(map, base+((adr+i)*ofs_factor));
120 }
121
122 /* Make sure it returns to read mode */
David Woodhousec314dfd2008-08-07 11:55:07 +0100123 cfi_qry_mode_off(base, map, cfi);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700124
125#ifdef CONFIG_MTD_XIP
126 (void) map_read(map, base);
Paulius Zaleckasca5c23c2008-02-27 01:42:39 +0200127 xip_iprefetch();
Linus Torvalds1da177e2005-04-16 15:20:36 -0700128 local_irq_enable();
129#endif
130
Linus Torvalds1da177e2005-04-16 15:20:36 -0700131 out: return extp;
132}
133
134EXPORT_SYMBOL(cfi_read_pri);
135
136void cfi_fixup(struct mtd_info *mtd, struct cfi_fixup *fixups)
137{
138 struct map_info *map = mtd->priv;
139 struct cfi_private *cfi = map->fldrv_priv;
140 struct cfi_fixup *f;
141
142 for (f=fixups; f->fixup; f++) {
143 if (((f->mfr == CFI_MFR_ANY) || (f->mfr == cfi->mfr)) &&
144 ((f->id == CFI_ID_ANY) || (f->id == cfi->id))) {
145 f->fixup(mtd, f->param);
146 }
147 }
148}
149
150EXPORT_SYMBOL(cfi_fixup);
151
152int cfi_varsize_frob(struct mtd_info *mtd, varsize_frob_t frob,
153 loff_t ofs, size_t len, void *thunk)
154{
155 struct map_info *map = mtd->priv;
156 struct cfi_private *cfi = map->fldrv_priv;
157 unsigned long adr;
158 int chipnum, ret = 0;
159 int i, first;
160 struct mtd_erase_region_info *regions = mtd->eraseregions;
161
162 if (ofs > mtd->size)
163 return -EINVAL;
164
165 if ((len + ofs) > mtd->size)
166 return -EINVAL;
167
168 /* Check that both start and end of the requested erase are
169 * aligned with the erasesize at the appropriate addresses.
170 */
171
172 i = 0;
173
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000174 /* Skip all erase regions which are ended before the start of
Linus Torvalds1da177e2005-04-16 15:20:36 -0700175 the requested erase. Actually, to save on the calculations,
176 we skip to the first erase region which starts after the
177 start of the requested erase, and then go back one.
178 */
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000179
Linus Torvalds1da177e2005-04-16 15:20:36 -0700180 while (i < mtd->numeraseregions && ofs >= regions[i].offset)
181 i++;
182 i--;
183
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000184 /* OK, now i is pointing at the erase region in which this
Linus Torvalds1da177e2005-04-16 15:20:36 -0700185 erase request starts. Check the start of the requested
186 erase range is aligned with the erase size which is in
187 effect here.
188 */
189
190 if (ofs & (regions[i].erasesize-1))
191 return -EINVAL;
192
193 /* Remember the erase region we start on */
194 first = i;
195
196 /* Next, check that the end of the requested erase is aligned
197 * with the erase region at that address.
198 */
199
200 while (i<mtd->numeraseregions && (ofs + len) >= regions[i].offset)
201 i++;
202
203 /* As before, drop back one to point at the region in which
204 the address actually falls
205 */
206 i--;
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000207
Linus Torvalds1da177e2005-04-16 15:20:36 -0700208 if ((ofs + len) & (regions[i].erasesize-1))
209 return -EINVAL;
210
211 chipnum = ofs >> cfi->chipshift;
212 adr = ofs - (chipnum << cfi->chipshift);
213
214 i=first;
215
216 while(len) {
217 int size = regions[i].erasesize;
218
219 ret = (*frob)(map, &cfi->chips[chipnum], adr, size, thunk);
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000220
Linus Torvalds1da177e2005-04-16 15:20:36 -0700221 if (ret)
222 return ret;
223
224 adr += size;
225 ofs += size;
226 len -= size;
227
228 if (ofs == regions[i].offset + size * regions[i].numblocks)
229 i++;
230
231 if (adr >> cfi->chipshift) {
232 adr = 0;
233 chipnum++;
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000234
Linus Torvalds1da177e2005-04-16 15:20:36 -0700235 if (chipnum >= cfi->numchips)
236 break;
237 }
238 }
239
240 return 0;
241}
242
243EXPORT_SYMBOL(cfi_varsize_frob);
244
245MODULE_LICENSE("GPL");