blob: 09c79bd0b4f4fbdfd16f87fab4cb5687be070209 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * Common Flash Interface support:
Lucas De Marchi25985ed2011-03-30 22:57:33 -03003 * Generic utility functions not dependent on command set
Linus Torvalds1da177e2005-04-16 15:20:36 -07004 *
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>
Linus Torvalds1da177e2005-04-16 15:20:36 -070025
David Woodhousec314dfd2008-08-07 11:55:07 +010026int __xipram cfi_qry_present(struct map_info *map, __u32 base,
27 struct cfi_private *cfi)
Alexey Korolev2e489e02008-08-05 16:39:42 +010028{
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}
David Woodhousec314dfd2008-08-07 11:55:07 +010052EXPORT_SYMBOL_GPL(cfi_qry_present);
Alexey Korolev2e489e02008-08-05 16:39:42 +010053
David Woodhousec314dfd2008-08-07 11:55:07 +010054int __xipram cfi_qry_mode_on(uint32_t base, struct map_info *map,
55 struct cfi_private *cfi)
Alexey Korolev2e489e02008-08-05 16:39:42 +010056{
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);
David Woodhousec314dfd2008-08-07 11:55:07 +010059 if (cfi_qry_present(map, base, cfi))
Alexey Korolev2e489e02008-08-05 16:39:42 +010060 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);
David Woodhousec314dfd2008-08-07 11:55:07 +010066 if (cfi_qry_present(map, base, cfi))
Alexey Korolev2e489e02008-08-05 16:39:42 +010067 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);
David Woodhousec314dfd2008-08-07 11:55:07 +010071 if (cfi_qry_present(map, base, cfi))
Alexey Korolev2e489e02008-08-05 16:39:42 +010072 return 1;
Guillaume LECERF4a589482009-11-23 02:10:49 +010073 /* 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);
78 if (cfi_qry_present(map, base, cfi))
79 return 1;
Guillaume LECERFfc610152010-10-26 10:26:26 +010080 /* 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);
85 if (cfi_qry_present(map, base, cfi))
86 return 1;
Alexey Korolev2e489e02008-08-05 16:39:42 +010087 /* QRY not found */
88 return 0;
89}
David Woodhousec314dfd2008-08-07 11:55:07 +010090EXPORT_SYMBOL_GPL(cfi_qry_mode_on);
91
92void __xipram cfi_qry_mode_off(uint32_t base, struct map_info *map,
93 struct cfi_private *cfi)
Alexey Korolev2e489e02008-08-05 16:39:42 +010094{
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);
Massimo Cirillo23af51e2009-09-03 16:34:39 +020097 /* 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);
Alexey Korolev2e489e02008-08-05 16:39:42 +0100101}
David Woodhousec314dfd2008-08-07 11:55:07 +0100102EXPORT_SYMBOL_GPL(cfi_qry_mode_off);
Alexey Korolev2e489e02008-08-05 16:39:42 +0100103
Linus Torvalds1da177e2005-04-16 15:20:36 -0700104struct 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
Linus Torvalds1da177e2005-04-16 15:20:36 -0700113 if (!adr)
114 goto out;
115
Guillaume LECERF087444d2010-04-24 17:58:32 +0200116 printk(KERN_INFO "%s Extended Query Table at 0x%4.4X\n", name, adr);
117
Linus Torvalds1da177e2005-04-16 15:20:36 -0700118 extp = kmalloc(size, GFP_KERNEL);
Jingoo Han5c8b1fb2014-02-06 15:19:35 +0900119 if (!extp)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700120 goto out;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700121
122#ifdef CONFIG_MTD_XIP
123 local_irq_disable();
124#endif
125
126 /* Switch it into Query Mode */
David Woodhousec314dfd2008-08-07 11:55:07 +0100127 cfi_qry_mode_on(base, map, cfi);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700128 /* Read in the Extended Query Table */
129 for (i=0; i<size; i++) {
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000130 ((unsigned char *)extp)[i] =
Linus Torvalds1da177e2005-04-16 15:20:36 -0700131 cfi_read_query(map, base+((adr+i)*ofs_factor));
132 }
133
134 /* Make sure it returns to read mode */
David Woodhousec314dfd2008-08-07 11:55:07 +0100135 cfi_qry_mode_off(base, map, cfi);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700136
137#ifdef CONFIG_MTD_XIP
138 (void) map_read(map, base);
Paulius Zaleckasca5c23c2008-02-27 01:42:39 +0200139 xip_iprefetch();
Linus Torvalds1da177e2005-04-16 15:20:36 -0700140 local_irq_enable();
141#endif
142
Linus Torvalds1da177e2005-04-16 15:20:36 -0700143 out: return extp;
144}
145
146EXPORT_SYMBOL(cfi_read_pri);
147
148void cfi_fixup(struct mtd_info *mtd, struct cfi_fixup *fixups)
149{
150 struct map_info *map = mtd->priv;
151 struct cfi_private *cfi = map->fldrv_priv;
152 struct cfi_fixup *f;
153
154 for (f=fixups; f->fixup; f++) {
155 if (((f->mfr == CFI_MFR_ANY) || (f->mfr == cfi->mfr)) &&
156 ((f->id == CFI_ID_ANY) || (f->id == cfi->id))) {
Guillaume LECERFcc318222010-11-17 12:35:50 +0100157 f->fixup(mtd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700158 }
159 }
160}
161
162EXPORT_SYMBOL(cfi_fixup);
163
164int cfi_varsize_frob(struct mtd_info *mtd, varsize_frob_t frob,
165 loff_t ofs, size_t len, void *thunk)
166{
167 struct map_info *map = mtd->priv;
168 struct cfi_private *cfi = map->fldrv_priv;
169 unsigned long adr;
170 int chipnum, ret = 0;
171 int i, first;
172 struct mtd_erase_region_info *regions = mtd->eraseregions;
173
Linus Torvalds1da177e2005-04-16 15:20:36 -0700174 /* Check that both start and end of the requested erase are
175 * aligned with the erasesize at the appropriate addresses.
176 */
177
178 i = 0;
179
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000180 /* Skip all erase regions which are ended before the start of
Linus Torvalds1da177e2005-04-16 15:20:36 -0700181 the requested erase. Actually, to save on the calculations,
182 we skip to the first erase region which starts after the
183 start of the requested erase, and then go back one.
184 */
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000185
Linus Torvalds1da177e2005-04-16 15:20:36 -0700186 while (i < mtd->numeraseregions && ofs >= regions[i].offset)
187 i++;
188 i--;
189
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000190 /* OK, now i is pointing at the erase region in which this
Linus Torvalds1da177e2005-04-16 15:20:36 -0700191 erase request starts. Check the start of the requested
192 erase range is aligned with the erase size which is in
193 effect here.
194 */
195
196 if (ofs & (regions[i].erasesize-1))
197 return -EINVAL;
198
199 /* Remember the erase region we start on */
200 first = i;
201
202 /* Next, check that the end of the requested erase is aligned
203 * with the erase region at that address.
204 */
205
206 while (i<mtd->numeraseregions && (ofs + len) >= regions[i].offset)
207 i++;
208
209 /* As before, drop back one to point at the region in which
210 the address actually falls
211 */
212 i--;
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000213
Linus Torvalds1da177e2005-04-16 15:20:36 -0700214 if ((ofs + len) & (regions[i].erasesize-1))
215 return -EINVAL;
216
217 chipnum = ofs >> cfi->chipshift;
218 adr = ofs - (chipnum << cfi->chipshift);
219
220 i=first;
221
222 while(len) {
223 int size = regions[i].erasesize;
224
225 ret = (*frob)(map, &cfi->chips[chipnum], adr, size, thunk);
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000226
Linus Torvalds1da177e2005-04-16 15:20:36 -0700227 if (ret)
228 return ret;
229
230 adr += size;
231 ofs += size;
232 len -= size;
233
234 if (ofs == regions[i].offset + size * regions[i].numblocks)
235 i++;
236
237 if (adr >> cfi->chipshift) {
238 adr = 0;
239 chipnum++;
Thomas Gleixner1f948b42005-11-07 11:15:37 +0000240
Linus Torvalds1da177e2005-04-16 15:20:36 -0700241 if (chipnum >= cfi->numchips)
Dan Carpenter555b8d12014-05-14 17:19:00 +0300242 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700243 }
244 }
245
246 return 0;
247}
248
249EXPORT_SYMBOL(cfi_varsize_frob);
250
251MODULE_LICENSE("GPL");