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