xref: /openbmc/linux/drivers/mtd/chips/cfi_util.c (revision 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2)
1 /*
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.
9  *
10  * $Id: cfi_util.c,v 1.8 2004/12/14 19:55:56 nico Exp $
11  *
12  */
13 
14 #include <linux/module.h>
15 #include <linux/types.h>
16 #include <linux/kernel.h>
17 #include <linux/sched.h>
18 #include <asm/io.h>
19 #include <asm/byteorder.h>
20 
21 #include <linux/errno.h>
22 #include <linux/slab.h>
23 #include <linux/delay.h>
24 #include <linux/interrupt.h>
25 #include <linux/mtd/xip.h>
26 #include <linux/mtd/mtd.h>
27 #include <linux/mtd/map.h>
28 #include <linux/mtd/cfi.h>
29 #include <linux/mtd/compatmac.h>
30 
31 struct cfi_extquery *
32 __xipram cfi_read_pri(struct map_info *map, __u16 adr, __u16 size, const char* name)
33 {
34 	struct cfi_private *cfi = map->fldrv_priv;
35 	__u32 base = 0; // cfi->chips[0].start;
36 	int ofs_factor = cfi->interleave * cfi->device_type;
37 	int i;
38 	struct cfi_extquery *extp = NULL;
39 
40 	printk(" %s Extended Query Table at 0x%4.4X\n", name, adr);
41 	if (!adr)
42 		goto out;
43 
44 	extp = kmalloc(size, GFP_KERNEL);
45 	if (!extp) {
46 		printk(KERN_ERR "Failed to allocate memory\n");
47 		goto out;
48 	}
49 
50 #ifdef CONFIG_MTD_XIP
51 	local_irq_disable();
52 #endif
53 
54 	/* Switch it into Query Mode */
55 	cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL);
56 
57 	/* Read in the Extended Query Table */
58 	for (i=0; i<size; i++) {
59 		((unsigned char *)extp)[i] =
60 			cfi_read_query(map, base+((adr+i)*ofs_factor));
61 	}
62 
63 	/* Make sure it returns to read mode */
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 
67 #ifdef CONFIG_MTD_XIP
68 	(void) map_read(map, base);
69 	asm volatile (".rep 8; nop; .endr");
70 	local_irq_enable();
71 #endif
72 
73 	if (extp->MajorVersion != '1' ||
74 	    (extp->MinorVersion < '0' || extp->MinorVersion > '3')) {
75 		printk(KERN_WARNING "  Unknown %s Extended Query "
76 		       "version %c.%c.\n",  name, extp->MajorVersion,
77 		       extp->MinorVersion);
78 		kfree(extp);
79 		extp = NULL;
80 	}
81 
82  out:	return extp;
83 }
84 
85 EXPORT_SYMBOL(cfi_read_pri);
86 
87 void cfi_fixup(struct mtd_info *mtd, struct cfi_fixup *fixups)
88 {
89 	struct map_info *map = mtd->priv;
90 	struct cfi_private *cfi = map->fldrv_priv;
91 	struct cfi_fixup *f;
92 
93 	for (f=fixups; f->fixup; f++) {
94 		if (((f->mfr == CFI_MFR_ANY) || (f->mfr == cfi->mfr)) &&
95 		    ((f->id  == CFI_ID_ANY)  || (f->id  == cfi->id))) {
96 			f->fixup(mtd, f->param);
97 		}
98 	}
99 }
100 
101 EXPORT_SYMBOL(cfi_fixup);
102 
103 int cfi_varsize_frob(struct mtd_info *mtd, varsize_frob_t frob,
104 				     loff_t ofs, size_t len, void *thunk)
105 {
106 	struct map_info *map = mtd->priv;
107 	struct cfi_private *cfi = map->fldrv_priv;
108 	unsigned long adr;
109 	int chipnum, ret = 0;
110 	int i, first;
111 	struct mtd_erase_region_info *regions = mtd->eraseregions;
112 
113 	if (ofs > mtd->size)
114 		return -EINVAL;
115 
116 	if ((len + ofs) > mtd->size)
117 		return -EINVAL;
118 
119 	/* Check that both start and end of the requested erase are
120 	 * aligned with the erasesize at the appropriate addresses.
121 	 */
122 
123 	i = 0;
124 
125 	/* Skip all erase regions which are ended before the start of
126 	   the requested erase. Actually, to save on the calculations,
127 	   we skip to the first erase region which starts after the
128 	   start of the requested erase, and then go back one.
129 	*/
130 
131 	while (i < mtd->numeraseregions && ofs >= regions[i].offset)
132 	       i++;
133 	i--;
134 
135 	/* OK, now i is pointing at the erase region in which this
136 	   erase request starts. Check the start of the requested
137 	   erase range is aligned with the erase size which is in
138 	   effect here.
139 	*/
140 
141 	if (ofs & (regions[i].erasesize-1))
142 		return -EINVAL;
143 
144 	/* Remember the erase region we start on */
145 	first = i;
146 
147 	/* Next, check that the end of the requested erase is aligned
148 	 * with the erase region at that address.
149 	 */
150 
151 	while (i<mtd->numeraseregions && (ofs + len) >= regions[i].offset)
152 		i++;
153 
154 	/* As before, drop back one to point at the region in which
155 	   the address actually falls
156 	*/
157 	i--;
158 
159 	if ((ofs + len) & (regions[i].erasesize-1))
160 		return -EINVAL;
161 
162 	chipnum = ofs >> cfi->chipshift;
163 	adr = ofs - (chipnum << cfi->chipshift);
164 
165 	i=first;
166 
167 	while(len) {
168 		int size = regions[i].erasesize;
169 
170 		ret = (*frob)(map, &cfi->chips[chipnum], adr, size, thunk);
171 
172 		if (ret)
173 			return ret;
174 
175 		adr += size;
176 		ofs += size;
177 		len -= size;
178 
179 		if (ofs == regions[i].offset + size * regions[i].numblocks)
180 			i++;
181 
182 		if (adr >> cfi->chipshift) {
183 			adr = 0;
184 			chipnum++;
185 
186 			if (chipnum >= cfi->numchips)
187 			break;
188 		}
189 	}
190 
191 	return 0;
192 }
193 
194 EXPORT_SYMBOL(cfi_varsize_frob);
195 
196 MODULE_LICENSE("GPL");
197