]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/flash/at45d.c
Merge clang trunk r321017 to contrib/llvm/tools/clang.
[FreeBSD/FreeBSD.git] / sys / dev / flash / at45d.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2006 M. Warner Losh
5  * Copyright (c) 2011-2012 Ian Lepore
6  * Copyright (c) 2012 Marius Strobl <marius@FreeBSD.org>
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32
33 #include <sys/param.h>
34 #include <sys/systm.h>
35 #include <sys/bio.h>
36 #include <sys/bus.h>
37 #include <sys/conf.h>
38 #include <sys/kernel.h>
39 #include <sys/kthread.h>
40 #include <sys/lock.h>
41 #include <sys/mbuf.h>
42 #include <sys/malloc.h>
43 #include <sys/module.h>
44 #include <sys/mutex.h>
45 #include <geom/geom_disk.h>
46
47 #include <dev/spibus/spi.h>
48 #include "spibus_if.h"
49
50 struct at45d_flash_ident
51 {
52         const char      *name;
53         uint32_t        jedec;
54         uint16_t        pagecount;
55         uint16_t        pageoffset;
56         uint16_t        pagesize;
57         uint16_t        pagesize2n;
58 };
59
60 struct at45d_softc
61 {
62         struct bio_queue_head   bio_queue;
63         struct mtx              sc_mtx;
64         struct disk             *disk;
65         struct proc             *p;
66         struct intr_config_hook config_intrhook;
67         device_t                dev;
68         uint16_t                pagecount;
69         uint16_t                pageoffset;
70         uint16_t                pagesize;
71 };
72
73 #define AT45D_LOCK(_sc)                 mtx_lock(&(_sc)->sc_mtx)
74 #define AT45D_UNLOCK(_sc)               mtx_unlock(&(_sc)->sc_mtx)
75 #define AT45D_LOCK_INIT(_sc) \
76         mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
77             "at45d", MTX_DEF)
78 #define AT45D_LOCK_DESTROY(_sc)         mtx_destroy(&_sc->sc_mtx);
79 #define AT45D_ASSERT_LOCKED(_sc)        mtx_assert(&_sc->sc_mtx, MA_OWNED);
80 #define AT45D_ASSERT_UNLOCKED(_sc)      mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
81
82 /* bus entry points */
83 static device_attach_t at45d_attach;
84 static device_detach_t at45d_detach;
85 static device_probe_t at45d_probe;
86
87 /* disk routines */
88 static int at45d_close(struct disk *dp);
89 static int at45d_open(struct disk *dp);
90 static void at45d_strategy(struct bio *bp);
91 static void at45d_task(void *arg);
92
93 /* helper routines */
94 static void at45d_delayed_attach(void *xsc);
95 static int at45d_get_mfg_info(device_t dev, uint8_t *resp);
96 static int at45d_get_status(device_t dev, uint8_t *status);
97 static int at45d_wait_ready(device_t dev, uint8_t *status);
98
99 #define BUFFER_TRANSFER                 0x53
100 #define BUFFER_COMPARE                  0x60
101 #define PROGRAM_THROUGH_BUFFER          0x82
102 #define MANUFACTURER_ID                 0x9f
103 #define STATUS_REGISTER_READ            0xd7
104 #define CONTINUOUS_ARRAY_READ           0xe8
105
106 /*
107  * A sectorsize2n != 0 is used to indicate that a device optionally supports
108  * 2^N byte pages.  If support for the latter is enabled, the sector offset
109  * has to be reduced by one.
110  */
111 static const struct at45d_flash_ident at45d_flash_devices[] = {
112         { "AT45DB011B", 0x1f2200, 512, 9, 264, 256 },
113         { "AT45DB021B", 0x1f2300, 1024, 9, 264, 256 },
114         { "AT45DB041x", 0x1f2400, 2028, 9, 264, 256 },
115         { "AT45DB081B", 0x1f2500, 4096, 9, 264, 256 },
116         { "AT45DB161x", 0x1f2600, 4096, 10, 528, 512 },
117         { "AT45DB321x", 0x1f2700, 8192, 10, 528, 0 },
118         { "AT45DB321x", 0x1f2701, 8192, 10, 528, 512 },
119         { "AT45DB642x", 0x1f2800, 8192, 11, 1056, 1024 }
120 };
121
122 static int
123 at45d_get_status(device_t dev, uint8_t *status)
124 {
125         uint8_t rxBuf[8], txBuf[8];
126         struct spi_command cmd;
127         int err;
128
129         memset(&cmd, 0, sizeof(cmd));
130         memset(txBuf, 0, sizeof(txBuf));
131         memset(rxBuf, 0, sizeof(rxBuf));
132
133         txBuf[0] = STATUS_REGISTER_READ;
134         cmd.tx_cmd = txBuf;
135         cmd.rx_cmd = rxBuf;
136         cmd.rx_cmd_sz = cmd.tx_cmd_sz = 2;
137         err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
138         *status = rxBuf[1];
139         return (err);
140 }
141
142 static int
143 at45d_get_mfg_info(device_t dev, uint8_t *resp)
144 {
145         uint8_t rxBuf[8], txBuf[8];
146         struct spi_command cmd;
147         int err;
148
149         memset(&cmd, 0, sizeof(cmd));
150         memset(txBuf, 0, sizeof(txBuf));
151         memset(rxBuf, 0, sizeof(rxBuf));
152
153         txBuf[0] = MANUFACTURER_ID;
154         cmd.tx_cmd = &txBuf;
155         cmd.rx_cmd = &rxBuf;
156         cmd.tx_cmd_sz = cmd.rx_cmd_sz = 5;
157         err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
158         if (err)
159                 return (err);
160         memcpy(resp, rxBuf + 1, 4);
161         return (0);
162 }
163
164 static int
165 at45d_wait_ready(device_t dev, uint8_t *status)
166 {
167         struct timeval now, tout;
168         int err;
169
170         getmicrouptime(&tout);
171         tout.tv_sec += 3;
172         do {
173                 getmicrouptime(&now);
174                 if (now.tv_sec > tout.tv_sec)
175                         err = ETIMEDOUT;
176                 else
177                         err = at45d_get_status(dev, status);
178         } while (err == 0 && (*status & 0x80) == 0);
179         return (err);
180 }
181
182 static int
183 at45d_probe(device_t dev)
184 {
185
186         device_set_desc(dev, "AT45D Flash Family");
187         return (0);
188 }
189
190 static int
191 at45d_attach(device_t dev)
192 {
193         struct at45d_softc *sc;
194
195         sc = device_get_softc(dev);
196         sc->dev = dev;
197         AT45D_LOCK_INIT(sc);
198
199         /* We'll see what kind of flash we have later... */
200         sc->config_intrhook.ich_func = at45d_delayed_attach;
201         sc->config_intrhook.ich_arg = sc;
202         if (config_intrhook_establish(&sc->config_intrhook) != 0) {
203                 device_printf(dev, "config_intrhook_establish failed\n");
204                 return (ENOMEM);
205         }
206         return (0);
207 }
208
209 static int
210 at45d_detach(device_t dev)
211 {
212
213         return (EBUSY) /* XXX */;
214 }
215
216 static void
217 at45d_delayed_attach(void *xsc)
218 {
219         struct at45d_softc *sc;
220         const struct at45d_flash_ident *ident;
221         u_int i;
222         uint32_t jedec;
223         uint16_t pagesize;
224         uint8_t buf[4], status;
225
226         sc = xsc;
227         ident = NULL;
228         jedec = 0;
229
230         if (at45d_wait_ready(sc->dev, &status) != 0)
231                 device_printf(sc->dev, "Error waiting for device-ready.\n");
232         else if (at45d_get_mfg_info(sc->dev, buf) != 0)
233                 device_printf(sc->dev, "Failed to get ID.\n");
234         else {
235                 jedec = buf[0] << 16 | buf[1] << 8 | buf[2];
236                 for (i = 0; i < nitems(at45d_flash_devices); i++) {
237                         if (at45d_flash_devices[i].jedec == jedec) {
238                                 ident = &at45d_flash_devices[i];
239                                 break;
240                         }
241                 }
242         }
243         if (ident == NULL)
244                 device_printf(sc->dev, "JEDEC 0x%x not in list.\n", jedec);
245         else {
246                 sc->pagecount = ident->pagecount;
247                 sc->pageoffset = ident->pageoffset;
248                 if (ident->pagesize2n != 0 && (status & 0x01) != 0) {
249                         sc->pageoffset -= 1;
250                         pagesize = ident->pagesize2n;
251                 } else
252                         pagesize = ident->pagesize;
253                 sc->pagesize = pagesize;
254
255                 sc->disk = disk_alloc();
256                 sc->disk->d_open = at45d_open;
257                 sc->disk->d_close = at45d_close;
258                 sc->disk->d_strategy = at45d_strategy;
259                 sc->disk->d_name = "flash/spi";
260                 sc->disk->d_drv1 = sc;
261                 sc->disk->d_maxsize = DFLTPHYS;
262                 sc->disk->d_sectorsize = pagesize;
263                 sc->disk->d_mediasize = pagesize * ident->pagecount;
264                 sc->disk->d_unit = device_get_unit(sc->dev);
265                 disk_create(sc->disk, DISK_VERSION);
266                 bioq_init(&sc->bio_queue);
267                 kproc_create(&at45d_task, sc, &sc->p, 0, 0,
268                     "task: at45d flash");
269                 device_printf(sc->dev, "%s, %d bytes per page, %d pages\n",
270                     ident->name, pagesize, ident->pagecount);
271         }
272
273         config_intrhook_disestablish(&sc->config_intrhook);
274 }
275
276 static int
277 at45d_open(struct disk *dp)
278 {
279
280         return (0);
281 }
282
283 static int
284 at45d_close(struct disk *dp)
285 {
286
287         return (0);
288 }
289
290 static void
291 at45d_strategy(struct bio *bp)
292 {
293         struct at45d_softc *sc;
294
295         sc = (struct at45d_softc *)bp->bio_disk->d_drv1;
296         AT45D_LOCK(sc);
297         bioq_disksort(&sc->bio_queue, bp);
298         wakeup(sc);
299         AT45D_UNLOCK(sc);
300 }
301
302 static void
303 at45d_task(void *arg)
304 {
305         uint8_t rxBuf[8], txBuf[8];
306         struct at45d_softc *sc;
307         struct bio *bp;
308         struct spi_command cmd;
309         device_t dev, pdev;
310         caddr_t buf;
311         u_long len, resid;
312         u_int addr, berr, err, offset, page;
313         uint8_t status;
314
315         sc = (struct at45d_softc*)arg;
316         dev = sc->dev;
317         pdev = device_get_parent(dev);
318         memset(&cmd, 0, sizeof(cmd));
319         memset(txBuf, 0, sizeof(txBuf));
320         memset(rxBuf, 0, sizeof(rxBuf));
321         cmd.tx_cmd = txBuf;
322         cmd.rx_cmd = rxBuf;
323
324         for (;;) {
325                 AT45D_LOCK(sc);
326                 do {
327                         bp = bioq_takefirst(&sc->bio_queue);
328                         if (bp == NULL)
329                                 msleep(sc, &sc->sc_mtx, PRIBIO, "jobqueue", 0);
330                 } while (bp == NULL);
331                 AT45D_UNLOCK(sc);
332
333                 berr = 0;
334                 buf = bp->bio_data;
335                 len = resid = bp->bio_bcount;
336                 page = bp->bio_offset / sc->pagesize;
337                 offset = bp->bio_offset % sc->pagesize;
338
339                 switch (bp->bio_cmd) {
340                 case BIO_READ:
341                         txBuf[0] = CONTINUOUS_ARRAY_READ;
342                         cmd.tx_cmd_sz = cmd.rx_cmd_sz = 8;
343                         cmd.tx_data = cmd.rx_data = buf;
344                         break;
345                 case BIO_WRITE:
346                         cmd.tx_cmd_sz = cmd.rx_cmd_sz = 4;
347                         cmd.tx_data = cmd.rx_data = buf;
348                         if (resid + offset > sc->pagesize)
349                                 len = sc->pagesize - offset;
350                         break;
351                 default:
352                         berr = EINVAL;
353                         goto out;
354                 }
355
356                 /*
357                  * NB: for BIO_READ, this loop is only traversed once.
358                  */
359                 while (resid > 0) {
360                         if (page > sc->pagecount) {
361                                 berr = EINVAL;
362                                 goto out;
363                         }
364                         addr = page << sc->pageoffset;
365                         if (bp->bio_cmd == BIO_WRITE) {
366                                 if (len != sc->pagesize) {
367                                         txBuf[0] = BUFFER_TRANSFER;
368                                         txBuf[1] = ((addr >> 16) & 0xff);
369                                         txBuf[2] = ((addr >> 8) & 0xff);
370                                         txBuf[3] = 0;
371                                         cmd.tx_data_sz = cmd.rx_data_sz = 0;
372                                         err = SPIBUS_TRANSFER(pdev, dev,
373                                             &cmd);
374                                         if (err == 0)
375                                                 err = at45d_wait_ready(dev,
376                                                     &status);
377                                         if (err != 0) {
378                                                 berr = EIO;
379                                                 goto out;
380                                         }
381                                 }
382                                 txBuf[0] = PROGRAM_THROUGH_BUFFER;
383                         }
384
385                         addr += offset;
386                         txBuf[1] = ((addr >> 16) & 0xff);
387                         txBuf[2] = ((addr >> 8) & 0xff);
388                         txBuf[3] = (addr & 0xff);
389                         cmd.tx_data_sz = cmd.rx_data_sz = len;
390                         err = SPIBUS_TRANSFER(pdev, dev, &cmd);
391                         if (err == 0 && bp->bio_cmd != BIO_READ)
392                                 err = at45d_wait_ready(dev, &status);
393                         if (err != 0) {
394                                 berr = EIO;
395                                 goto out;
396                         }
397                         if (bp->bio_cmd == BIO_WRITE) {
398                                 addr = page << sc->pageoffset;
399                                 txBuf[0] = BUFFER_COMPARE;
400                                 txBuf[1] = ((addr >> 16) & 0xff);
401                                 txBuf[2] = ((addr >> 8) & 0xff);
402                                 txBuf[3] = 0;
403                                 cmd.tx_data_sz = cmd.rx_data_sz = 0;
404                                 err = SPIBUS_TRANSFER(pdev, dev, &cmd);
405                                 if (err == 0)
406                                         err = at45d_wait_ready(dev, &status);
407                                 if (err != 0 || (status & 0x40) != 0) {
408                                         device_printf(dev, "comparing page "
409                                             "%d failed (status=0x%x)\n", addr,
410                                             status);
411                                         berr = EIO;
412                                         goto out;
413                                 }
414                         }
415                         page++;
416                         buf += len;
417                         offset = 0;
418                         resid -= len;
419                         if (resid > sc->pagesize)
420                                 len = sc->pagesize;
421                         else
422                                 len = resid;
423                         cmd.tx_data = cmd.rx_data = buf;
424                 }
425  out:
426                 if (berr != 0) {
427                         bp->bio_flags |= BIO_ERROR;
428                         bp->bio_error = berr;
429                 }
430                 bp->bio_resid = resid;
431                 biodone(bp);
432         }
433 }
434
435 static devclass_t at45d_devclass;
436
437 static device_method_t at45d_methods[] = {
438         /* Device interface */
439         DEVMETHOD(device_probe,         at45d_probe),
440         DEVMETHOD(device_attach,        at45d_attach),
441         DEVMETHOD(device_detach,        at45d_detach),
442
443         DEVMETHOD_END
444 };
445
446 static driver_t at45d_driver = {
447         "at45d",
448         at45d_methods,
449         sizeof(struct at45d_softc),
450 };
451
452 DRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, NULL, NULL);