[brcm-2.4] fix serial flash support (#6442)
[openwrt.git] / target / linux / brcm-2.4 / files / drivers / mtd / devices / sflash.c
1 /*
2  * Broadcom SiliconBackplane chipcommon serial flash interface
3  *
4  * Copyright 2006, Broadcom Corporation      
5  * All Rights Reserved.      
6  *       
7  * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY      
8  * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM      
9  * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS      
10  * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE.      
11  *
12  * $Id$
13  */
14
15 #include <linux/config.h>
16 #include <linux/module.h>
17 #include <linux/slab.h>
18 #include <linux/ioport.h>
19 #include <linux/mtd/compatmac.h>
20 #include <linux/mtd/mtd.h>
21 #include <linux/mtd/partitions.h>
22 #include <linux/errno.h>
23 #include <linux/pci.h>
24 #include <linux/delay.h>
25 #include <asm/io.h>
26
27 #include <typedefs.h>
28 #include <osl.h>
29 // #include <bcmutils.h>
30 #include <bcmdevs.h>
31 #include <bcmnvram.h>
32 #include <sbutils.h>
33 #include <sbconfig.h>
34 #include <sbchipc.h>
35 #include <sflash.h>
36
37 #ifdef CONFIG_MTD_PARTITIONS
38 extern struct mtd_partition * init_mtd_partitions(struct mtd_info *mtd, size_t size);
39 #endif
40
41 struct sflash_mtd {
42         sb_t *sbh;
43         chipcregs_t *cc;
44         struct semaphore lock;
45         struct mtd_info mtd;
46         struct mtd_erase_region_info region;
47 };
48
49 /* Private global state */
50 static struct sflash_mtd sflash;
51
52 static int
53 sflash_mtd_poll(struct sflash_mtd *sflash, unsigned int offset, int timeout)
54 {
55         int now = jiffies;
56         int ret = 0;
57
58         for (;;) {
59                 if (!sflash_poll(sflash->sbh, sflash->cc, offset)) {
60                         ret = 0;
61                         break;
62                 }
63                 if (time_after(jiffies, now + timeout)) {
64                         printk(KERN_ERR "sflash: timeout\n");
65                         ret = -ETIMEDOUT;
66                         break;
67                 }
68                 if (current->need_resched) {
69                         set_current_state(TASK_UNINTERRUPTIBLE);
70                         schedule_timeout(timeout / 10);
71                 } else
72                         udelay(1);
73         }
74
75         return ret;
76 }
77
78 static int
79 sflash_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
80 {
81         struct sflash_mtd *sflash = (struct sflash_mtd *) mtd->priv;
82         int bytes, ret = 0;
83
84         /* Check address range */
85         if (len == 0){
86          *retlen = 0;
87                 return 0;
88  }
89         if (!len)
90                 return 0;
91         if ((from + len) > mtd->size)
92                 return -EINVAL;
93         
94         down(&sflash->lock);
95
96         *retlen = 0;
97         while (len) {
98                 if ((bytes = sflash_read(sflash->sbh, sflash->cc, (uint) from, len, buf)) < 0) {
99                         ret = bytes;
100                         break;
101                 }
102                 from += (loff_t) bytes;
103                 len -= bytes;
104                 buf += bytes;
105                 *retlen += bytes;
106         }
107
108         up(&sflash->lock);
109
110         return ret;
111 }
112
113 static int
114 sflash_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf)
115 {
116         struct sflash_mtd *sflash = (struct sflash_mtd *) mtd->priv;
117         int bytes, ret = 0;
118
119         /* Check address range */
120         if (len == 0){
121          *retlen = 0;
122                 return 0;
123  }
124         if (!len)
125                 return 0;
126         if ((to + len) > mtd->size)
127                 return -EINVAL;
128
129         down(&sflash->lock);
130
131         *retlen = 0;
132         while (len) {
133                 if ((bytes = sflash_write(sflash->sbh, sflash->cc, (uint)to, (uint)len, buf)) < 0) {
134                         ret = bytes;
135                         break;
136                 }
137                 if ((ret = sflash_mtd_poll(sflash, (unsigned int) to, HZ / 10)))
138                         break;
139                 to += (loff_t) bytes;
140                 len -= bytes;
141                 buf += bytes;
142                 *retlen += bytes;
143         }
144
145         up(&sflash->lock);
146
147         return ret;
148 }
149
150 static int
151 sflash_mtd_erase(struct mtd_info *mtd, struct erase_info *erase)
152 {
153         struct sflash_mtd *sflash = (struct sflash_mtd *) mtd->priv;
154         int i, j, ret = 0;
155         unsigned int addr, len;
156
157         /* Check address range */
158         if (!erase->len)
159                 return 0;
160         if ((erase->addr + erase->len) > mtd->size)
161                 return -EINVAL;
162
163         addr = erase->addr;
164         len = erase->len;
165
166         down(&sflash->lock);
167
168         /* Ensure that requested region is aligned */
169         for (i = 0; i < mtd->numeraseregions; i++) {
170                 for (j = 0; j < mtd->eraseregions[i].numblocks; j++) {
171                         if (addr == mtd->eraseregions[i].offset + mtd->eraseregions[i].erasesize * j &&
172                             len >= mtd->eraseregions[i].erasesize) {
173                                 if ((ret = sflash_erase(sflash->sbh, sflash->cc, addr)) < 0)
174                                         break;
175                                 if ((ret = sflash_mtd_poll(sflash, addr, 10 * HZ)))
176                                         break;
177                                 addr += mtd->eraseregions[i].erasesize;
178                                 len -= mtd->eraseregions[i].erasesize;
179                         }
180                 }
181                 if (ret)
182                         break;
183         }
184
185         up(&sflash->lock);
186
187         /* Set erase status */
188         if (ret)
189                 erase->state = MTD_ERASE_FAILED;
190         else 
191                 erase->state = MTD_ERASE_DONE;
192
193         /* Call erase callback */
194         if (erase->callback)
195                 erase->callback(erase);
196
197         return ret;
198 }
199
200 #if LINUX_VERSION_CODE < 0x20212 && defined(MODULE)
201 #define sflash_mtd_init init_module
202 #define sflash_mtd_exit cleanup_module
203 #endif
204
205 mod_init_t
206 sflash_mtd_init(void)
207 {
208         struct pci_dev *pdev;
209         int ret = 0;
210         struct sflash *info;
211         uint i;
212 #ifdef CONFIG_MTD_PARTITIONS
213         struct mtd_partition *parts;
214 #endif
215
216         if (!(pdev = pci_find_device(VENDOR_BROADCOM, SB_CC, NULL))) {
217                 printk(KERN_ERR "sflash: chipcommon not found\n");
218                 return -ENODEV;
219         }
220
221         memset(&sflash, 0, sizeof(struct sflash_mtd));
222         init_MUTEX(&sflash.lock);
223
224         /* attach to the backplane */
225         if (!(sflash.sbh = sb_kattach(SB_OSH))) {
226                 printk(KERN_ERR "sflash: error attaching to backplane\n");
227                 ret = -EIO;
228                 goto fail;
229         }
230
231         /* Map registers and flash base */
232         if (!(sflash.cc = ioremap_nocache(pci_resource_start(pdev, 0),
233                                           pci_resource_len(pdev, 0)))) {
234                 printk(KERN_ERR "sflash: error mapping registers\n");
235                 ret = -EIO;
236                 goto fail;
237         }
238
239         /* Initialize serial flash access */
240         if (!(info = sflash_init(sflash.sbh, sflash.cc))) {
241                 printk(KERN_ERR "sflash: found no supported devices\n");
242                 ret = -ENODEV;
243                 goto fail;
244         }
245
246         printk(KERN_INFO "sflash: found serial flash; blocksize=%dKB, numblocks=%d, size=%dKB\n",info->blocksize/1024,info->numblocks,info->size/1024);
247
248         /* Setup region info */
249         sflash.region.offset = 0;
250         sflash.region.erasesize = info->blocksize;
251         sflash.region.numblocks = info->numblocks;
252         if (sflash.region.erasesize > sflash.mtd.erasesize)
253                 sflash.mtd.erasesize = sflash.region.erasesize;
254         sflash.mtd.size = info->size;
255         sflash.mtd.numeraseregions = 1;
256
257         /* Register with MTD */
258         sflash.mtd.name = "sflash";
259         sflash.mtd.type = MTD_NORFLASH;
260         sflash.mtd.flags = MTD_CAP_NORFLASH;
261         sflash.mtd.eraseregions = &sflash.region;
262         sflash.mtd.module = THIS_MODULE;
263         sflash.mtd.erase = sflash_mtd_erase;
264         sflash.mtd.read = sflash_mtd_read;
265         sflash.mtd.write = sflash_mtd_write;
266         sflash.mtd.priv = &sflash;
267
268 #ifdef CONFIG_MTD_PARTITIONS
269         parts = init_mtd_partitions(&sflash.mtd, sflash.mtd.size);
270         for (i = 0; parts[i].name; i++);
271         ret = add_mtd_partitions(&sflash.mtd, parts, i);
272 #else
273         ret = add_mtd_device(&sflash.mtd);
274 #endif
275         if (ret) {
276                 printk(KERN_ERR "sflash: add_mtd failed\n");
277                 goto fail;
278         }
279
280         return 0;
281
282  fail:
283         if (sflash.cc)
284                 iounmap((void *) sflash.cc);
285         if (sflash.sbh)
286                 sb_detach(sflash.sbh);
287         return ret;
288 }
289
290 mod_exit_t
291 sflash_mtd_exit(void)
292 {
293 #ifdef CONFIG_MTD_PARTITIONS
294         del_mtd_partitions(&sflash.mtd);
295 #else
296         del_mtd_device(&sflash.mtd);
297 #endif
298         iounmap((void *) sflash.cc);
299         sb_detach(sflash.sbh);
300 }
301
302 module_init(sflash_mtd_init);
303 module_exit(sflash_mtd_exit);