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