at45d.c revision 237384
1164742Simp/*- 2237384Smarius * Copyright (c) 2006 M. Warner Losh 3237384Smarius * Copyright (c) 2011-2012 Ian Lepore 4237384Smarius * Copyright (c) 2012 Marius Strobl <marius@FreeBSD.org> 5237384Smarius * All rights reserved. 6164742Simp * 7164742Simp * Redistribution and use in source and binary forms, with or without 8164742Simp * modification, are permitted provided that the following conditions 9164742Simp * are met: 10164742Simp * 1. Redistributions of source code must retain the above copyright 11164742Simp * notice, this list of conditions and the following disclaimer. 12164742Simp * 2. Redistributions in binary form must reproduce the above copyright 13164742Simp * notice, this list of conditions and the following disclaimer in the 14164742Simp * documentation and/or other materials provided with the distribution. 15164742Simp * 16164742Simp * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17164742Simp * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18164742Simp * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19164742Simp * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20164742Simp * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21164742Simp * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22164742Simp * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23164742Simp * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24164742Simp * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25164742Simp * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26164742Simp */ 27164742Simp 28164742Simp#include <sys/cdefs.h> 29164742Simp__FBSDID("$FreeBSD: stable/9/sys/dev/flash/at45d.c 237384 2012-06-21 11:16:05Z marius $"); 30164742Simp 31164742Simp#include <sys/param.h> 32164742Simp#include <sys/systm.h> 33164742Simp#include <sys/bio.h> 34164742Simp#include <sys/bus.h> 35164742Simp#include <sys/conf.h> 36164742Simp#include <sys/kernel.h> 37164742Simp#include <sys/kthread.h> 38164742Simp#include <sys/lock.h> 39164742Simp#include <sys/mbuf.h> 40164742Simp#include <sys/malloc.h> 41164742Simp#include <sys/module.h> 42164742Simp#include <sys/mutex.h> 43164742Simp#include <geom/geom_disk.h> 44164742Simp 45164742Simp#include <dev/spibus/spi.h> 46164742Simp#include "spibus_if.h" 47164742Simp 48237384Smariusstruct at45d_flash_ident 49164742Simp{ 50237384Smarius const char *name; 51237384Smarius uint32_t jedec; 52237384Smarius uint16_t pagecount; 53237384Smarius uint16_t pageoffset; 54237384Smarius uint16_t pagesize; 55237384Smarius uint16_t pagesize2n; 56164742Simp}; 57164742Simp 58237384Smariusstruct at45d_softc 59237384Smarius{ 60237384Smarius struct bio_queue_head bio_queue; 61237384Smarius struct mtx sc_mtx; 62237384Smarius struct disk *disk; 63237384Smarius struct proc *p; 64237384Smarius struct intr_config_hook config_intrhook; 65237384Smarius device_t dev; 66237384Smarius uint16_t pagecount; 67237384Smarius uint16_t pageoffset; 68237384Smarius uint16_t pagesize; 69237384Smarius}; 70237384Smarius 71237384Smarius#define AT45D_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) 72164742Simp#define AT45D_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) 73237384Smarius#define AT45D_LOCK_INIT(_sc) \ 74164742Simp mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ 75164742Simp "at45d", MTX_DEF) 76237384Smarius#define AT45D_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); 77237384Smarius#define AT45D_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED); 78237384Smarius#define AT45D_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED); 79164742Simp 80237384Smarius/* bus entry points */ 81237384Smariusstatic device_attach_t at45d_attach; 82237384Smariusstatic device_detach_t at45d_detach; 83237384Smariusstatic device_probe_t at45d_probe; 84164742Simp 85164742Simp/* disk routines */ 86237384Smariusstatic int at45d_close(struct disk *dp); 87164742Simpstatic int at45d_open(struct disk *dp); 88164742Simpstatic void at45d_strategy(struct bio *bp); 89164742Simpstatic void at45d_task(void *arg); 90164742Simp 91237384Smarius/* helper routines */ 92237384Smariusstatic void at45d_delayed_attach(void *xsc); 93237384Smariusstatic int at45d_get_mfg_info(device_t dev, uint8_t *resp); 94237384Smariusstatic int at45d_get_status(device_t dev, uint8_t *status); 95237384Smariusstatic int at45d_wait_ready(device_t dev, uint8_t *status); 96164742Simp 97237384Smarius#define BUFFER_TRANSFER 0x53 98237384Smarius#define BUFFER_COMPARE 0x60 99237384Smarius#define PROGRAM_THROUGH_BUFFER 0x82 100237384Smarius#define MANUFACTURER_ID 0x9f 101237384Smarius#define STATUS_REGISTER_READ 0xd7 102237384Smarius#define CONTINUOUS_ARRAY_READ 0xe8 103237384Smarius 104237384Smarius/* 105237384Smarius * A sectorsize2n != 0 is used to indicate that a device optionally supports 106237384Smarius * 2^N byte pages. If support for the latter is enabled, the sector offset 107237384Smarius * has to be reduced by one. 108237384Smarius */ 109237384Smariusstatic const struct at45d_flash_ident const at45d_flash_devices[] = { 110237384Smarius { "AT45DB011B", 0x1f2200, 512, 9, 264, 256 }, 111237384Smarius { "AT45DB021B", 0x1f2300, 1024, 9, 264, 256 }, 112237384Smarius { "AT45DB041x", 0x1f2400, 2028, 9, 264, 256 }, 113237384Smarius { "AT45DB081B", 0x1f2500, 4096, 9, 264, 256 }, 114237384Smarius { "AT45DB161x", 0x1f2600, 4096, 10, 528, 512 }, 115237384Smarius { "AT45DB321x", 0x1f2700, 8192, 10, 528, 0 }, 116237384Smarius { "AT45DB321x", 0x1f2701, 8192, 10, 528, 512 }, 117237384Smarius { "AT45DB642x", 0x1f2800, 8192, 11, 1056, 1024 } 118237384Smarius}; 119237384Smarius 120237384Smariusstatic int 121237384Smariusat45d_get_status(device_t dev, uint8_t *status) 122164742Simp{ 123237384Smarius uint8_t rxBuf[8], txBuf[8]; 124164742Simp struct spi_command cmd; 125164742Simp int err; 126164742Simp 127164742Simp memset(&cmd, 0, sizeof(cmd)); 128164742Simp memset(txBuf, 0, sizeof(txBuf)); 129164742Simp memset(rxBuf, 0, sizeof(rxBuf)); 130164742Simp 131164742Simp txBuf[0] = STATUS_REGISTER_READ; 132164742Simp cmd.tx_cmd = txBuf; 133164742Simp cmd.rx_cmd = rxBuf; 134237384Smarius cmd.rx_cmd_sz = cmd.tx_cmd_sz = 2; 135164742Simp err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd); 136237384Smarius *status = rxBuf[1]; 137237384Smarius return (err); 138164742Simp} 139164742Simp 140164742Simpstatic int 141164742Simpat45d_get_mfg_info(device_t dev, uint8_t *resp) 142164742Simp{ 143237384Smarius uint8_t rxBuf[8], txBuf[8]; 144164742Simp struct spi_command cmd; 145164742Simp int err; 146164742Simp 147164742Simp memset(&cmd, 0, sizeof(cmd)); 148164742Simp memset(txBuf, 0, sizeof(txBuf)); 149164742Simp memset(rxBuf, 0, sizeof(rxBuf)); 150164742Simp 151164742Simp txBuf[0] = MANUFACTURER_ID; 152164742Simp cmd.tx_cmd = &txBuf; 153164742Simp cmd.rx_cmd = &rxBuf; 154237384Smarius cmd.tx_cmd_sz = cmd.rx_cmd_sz = 5; 155164742Simp err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd); 156164742Simp if (err) 157164742Simp return (err); 158164742Simp memcpy(resp, rxBuf + 1, 4); 159164742Simp return (0); 160164742Simp} 161164742Simp 162164742Simpstatic int 163237384Smariusat45d_wait_ready(device_t dev, uint8_t *status) 164237384Smarius{ 165237384Smarius struct timeval now, tout; 166237384Smarius int err; 167237384Smarius 168237384Smarius getmicrouptime(&tout); 169237384Smarius tout.tv_sec += 3; 170237384Smarius do { 171237384Smarius getmicrouptime(&now); 172237384Smarius if (now.tv_sec > tout.tv_sec) 173237384Smarius err = ETIMEDOUT; 174237384Smarius else 175237384Smarius err = at45d_get_status(dev, status); 176237384Smarius } while (err == 0 && (*status & 0x80) == 0); 177237384Smarius return (err); 178237384Smarius} 179237384Smarius 180237384Smariusstatic int 181164742Simpat45d_probe(device_t dev) 182164742Simp{ 183237384Smarius 184237384Smarius device_set_desc(dev, "AT45D Flash Family"); 185164742Simp return (0); 186164742Simp} 187164742Simp 188164742Simpstatic int 189164742Simpat45d_attach(device_t dev) 190164742Simp{ 191164742Simp struct at45d_softc *sc; 192164742Simp 193164742Simp sc = device_get_softc(dev); 194164742Simp sc->dev = dev; 195164742Simp AT45D_LOCK_INIT(sc); 196164742Simp 197164742Simp /* We'll see what kind of flash we have later... */ 198164742Simp sc->config_intrhook.ich_func = at45d_delayed_attach; 199164742Simp sc->config_intrhook.ich_arg = sc; 200164742Simp if (config_intrhook_establish(&sc->config_intrhook) != 0) 201164742Simp device_printf(dev, "config_intrhook_establish failed\n"); 202164742Simp return (0); 203164742Simp} 204164742Simp 205164742Simpstatic int 206164742Simpat45d_detach(device_t dev) 207164742Simp{ 208237384Smarius 209237384Smarius return (EBUSY) /* XXX */; 210164742Simp} 211164742Simp 212164742Simpstatic void 213164742Simpat45d_delayed_attach(void *xsc) 214164742Simp{ 215237384Smarius struct at45d_softc *sc; 216237384Smarius const struct at45d_flash_ident *ident; 217237384Smarius u_int i; 218237384Smarius uint32_t jedec; 219237384Smarius uint16_t pagesize; 220237384Smarius uint8_t buf[4], status; 221164742Simp 222237384Smarius sc = xsc; 223237384Smarius ident = NULL; 224237384Smarius jedec = 0; 225164742Simp 226237384Smarius if (at45d_wait_ready(sc->dev, &status) != 0) 227237384Smarius device_printf(sc->dev, "Error waiting for device-ready.\n"); 228237384Smarius else if (at45d_get_mfg_info(sc->dev, buf) != 0) 229237384Smarius device_printf(sc->dev, "Failed to get ID.\n"); 230237384Smarius else { 231237384Smarius jedec = buf[0] << 16 | buf[1] << 8 | buf[2]; 232237384Smarius for (i = 0; i < nitems(at45d_flash_devices); i++) { 233237384Smarius if (at45d_flash_devices[i].jedec == jedec) { 234237384Smarius ident = &at45d_flash_devices[i]; 235237384Smarius break; 236237384Smarius } 237237384Smarius } 238237384Smarius } 239237384Smarius if (ident == NULL) 240237384Smarius device_printf(sc->dev, "JEDEC 0x%x not in list.\n", jedec); 241237384Smarius else { 242237384Smarius sc->pagecount = ident->pagecount; 243237384Smarius sc->pageoffset = ident->pageoffset; 244237384Smarius if (ident->pagesize2n != 0 && (status & 0x01) != 0) { 245237384Smarius sc->pageoffset -= 1; 246237384Smarius pagesize = ident->pagesize2n; 247237384Smarius } else 248237384Smarius pagesize = ident->pagesize; 249237384Smarius sc->pagesize = pagesize; 250237384Smarius 251237384Smarius sc->disk = disk_alloc(); 252237384Smarius sc->disk->d_open = at45d_open; 253237384Smarius sc->disk->d_close = at45d_close; 254237384Smarius sc->disk->d_strategy = at45d_strategy; 255237384Smarius sc->disk->d_name = "flash/spi"; 256237384Smarius sc->disk->d_drv1 = sc; 257237384Smarius sc->disk->d_maxsize = DFLTPHYS; 258237384Smarius sc->disk->d_sectorsize = pagesize; 259237384Smarius sc->disk->d_mediasize = pagesize * ident->pagecount; 260237384Smarius sc->disk->d_unit = device_get_unit(sc->dev); 261237384Smarius disk_create(sc->disk, DISK_VERSION); 262237384Smarius bioq_init(&sc->bio_queue); 263237384Smarius kproc_create(&at45d_task, sc, &sc->p, 0, 0, 264237384Smarius "task: at45d flash"); 265237384Smarius device_printf(sc->dev, "%s, %d bytes per page, %d pages\n", 266237384Smarius ident->name, pagesize, ident->pagecount); 267237384Smarius } 268237384Smarius 269164742Simp config_intrhook_disestablish(&sc->config_intrhook); 270164742Simp} 271164742Simp 272164742Simpstatic int 273164742Simpat45d_open(struct disk *dp) 274164742Simp{ 275237384Smarius 276237384Smarius return (0); 277164742Simp} 278164742Simp 279164742Simpstatic int 280164742Simpat45d_close(struct disk *dp) 281164742Simp{ 282237384Smarius 283237384Smarius return (0); 284164742Simp} 285164742Simp 286164742Simpstatic void 287164742Simpat45d_strategy(struct bio *bp) 288164742Simp{ 289164742Simp struct at45d_softc *sc; 290164742Simp 291164742Simp sc = (struct at45d_softc *)bp->bio_disk->d_drv1; 292164742Simp AT45D_LOCK(sc); 293164742Simp bioq_disksort(&sc->bio_queue, bp); 294164742Simp wakeup(sc); 295164742Simp AT45D_UNLOCK(sc); 296164742Simp} 297164742Simp 298164742Simpstatic void 299164742Simpat45d_task(void *arg) 300164742Simp{ 301237384Smarius uint8_t rxBuf[8], txBuf[8]; 302237384Smarius struct at45d_softc *sc; 303164742Simp struct bio *bp; 304164742Simp struct spi_command cmd; 305164742Simp device_t dev, pdev; 306237384Smarius caddr_t buf; 307237384Smarius u_long len, resid; 308237384Smarius u_int addr, berr, err, offset, page; 309237384Smarius uint8_t status; 310164742Simp 311237384Smarius sc = (struct at45d_softc*)arg; 312237384Smarius dev = sc->dev; 313237384Smarius pdev = device_get_parent(dev); 314237384Smarius memset(&cmd, 0, sizeof(cmd)); 315237384Smarius memset(txBuf, 0, sizeof(txBuf)); 316237384Smarius memset(rxBuf, 0, sizeof(rxBuf)); 317237384Smarius cmd.tx_cmd = txBuf; 318237384Smarius cmd.rx_cmd = rxBuf; 319237384Smarius 320164742Simp for (;;) { 321164742Simp AT45D_LOCK(sc); 322164742Simp do { 323237384Smarius bp = bioq_takefirst(&sc->bio_queue); 324164742Simp if (bp == NULL) 325164742Simp msleep(sc, &sc->sc_mtx, PRIBIO, "jobqueue", 0); 326164742Simp } while (bp == NULL); 327164742Simp AT45D_UNLOCK(sc); 328237384Smarius 329237384Smarius berr = 0; 330237384Smarius buf = bp->bio_data; 331237384Smarius len = resid = bp->bio_bcount; 332237384Smarius page = bp->bio_offset / sc->pagesize; 333237384Smarius offset = bp->bio_offset % sc->pagesize; 334237384Smarius 335237384Smarius switch (bp->bio_cmd) { 336237384Smarius case BIO_READ: 337237384Smarius txBuf[0] = CONTINUOUS_ARRAY_READ; 338237384Smarius cmd.tx_cmd_sz = cmd.rx_cmd_sz = 8; 339237384Smarius cmd.tx_data = cmd.rx_data = buf; 340237384Smarius break; 341237384Smarius case BIO_WRITE: 342237384Smarius cmd.tx_cmd_sz = cmd.rx_cmd_sz = 4; 343237384Smarius cmd.tx_data = cmd.rx_data = buf; 344237384Smarius if (resid + offset > sc->pagesize) 345237384Smarius len = sc->pagesize - offset; 346237384Smarius break; 347237384Smarius default: 348237384Smarius berr = EINVAL; 349237384Smarius goto out; 350237384Smarius } 351237384Smarius 352237384Smarius /* 353237384Smarius * NB: for BIO_READ, this loop is only traversed once. 354237384Smarius */ 355237384Smarius while (resid > 0) { 356237384Smarius if (page > sc->pagecount) { 357237384Smarius berr = EINVAL; 358237384Smarius goto out; 359237384Smarius } 360237384Smarius addr = page << sc->pageoffset; 361237384Smarius if (bp->bio_cmd == BIO_WRITE) { 362237384Smarius if (len != sc->pagesize) { 363237384Smarius txBuf[0] = BUFFER_TRANSFER; 364237384Smarius txBuf[1] = ((addr >> 16) & 0xff); 365237384Smarius txBuf[2] = ((addr >> 8) & 0xff); 366237384Smarius txBuf[3] = 0; 367237384Smarius cmd.tx_data_sz = cmd.rx_data_sz = 0; 368237384Smarius err = SPIBUS_TRANSFER(pdev, dev, 369237384Smarius &cmd); 370237384Smarius if (err == 0) 371237384Smarius err = at45d_wait_ready(dev, 372237384Smarius &status); 373237384Smarius if (err != 0) { 374237384Smarius berr = EIO; 375237384Smarius goto out; 376237384Smarius } 377237384Smarius } 378164742Simp txBuf[0] = PROGRAM_THROUGH_BUFFER; 379164742Simp } 380237384Smarius 381237384Smarius addr += offset; 382237384Smarius txBuf[1] = ((addr >> 16) & 0xff); 383237384Smarius txBuf[2] = ((addr >> 8) & 0xff); 384237384Smarius txBuf[3] = (addr & 0xff); 385237384Smarius cmd.tx_data_sz = cmd.rx_data_sz = len; 386164742Simp err = SPIBUS_TRANSFER(pdev, dev, &cmd); 387237384Smarius if (err == 0 && bp->bio_cmd != BIO_READ) 388237384Smarius err = at45d_wait_ready(dev, &status); 389237384Smarius if (err != 0) { 390237384Smarius berr = EIO; 391237384Smarius goto out; 392237384Smarius } 393237384Smarius if (bp->bio_cmd == BIO_WRITE) { 394237384Smarius addr = page << sc->pageoffset; 395237384Smarius txBuf[0] = BUFFER_COMPARE; 396237384Smarius txBuf[1] = ((addr >> 16) & 0xff); 397237384Smarius txBuf[2] = ((addr >> 8) & 0xff); 398237384Smarius txBuf[3] = 0; 399237384Smarius cmd.tx_data_sz = cmd.rx_data_sz = 0; 400237384Smarius err = SPIBUS_TRANSFER(pdev, dev, &cmd); 401237384Smarius if (err == 0) 402237384Smarius err = at45d_wait_ready(dev, &status); 403237384Smarius if (err != 0 || (status & 0x40) != 0) { 404237384Smarius device_printf(dev, "comparing page " 405237384Smarius "%d failed (status=0x%x)\n", addr, 406237384Smarius status); 407237384Smarius berr = EIO; 408237384Smarius goto out; 409237384Smarius } 410237384Smarius } 411237384Smarius page++; 412237384Smarius buf += len; 413237384Smarius offset = 0; 414237384Smarius resid -= len; 415237384Smarius if (resid > sc->pagesize) 416237384Smarius len = sc->pagesize; 417237384Smarius else 418237384Smarius len = resid; 419237384Smarius cmd.tx_data = cmd.rx_data = buf; 420164742Simp } 421237384Smarius out: 422237384Smarius if (berr != 0) { 423237384Smarius bp->bio_flags |= BIO_ERROR; 424237384Smarius bp->bio_error = berr; 425237384Smarius } 426237384Smarius bp->bio_resid = resid; 427164742Simp biodone(bp); 428164742Simp } 429164742Simp} 430164742Simp 431164742Simpstatic devclass_t at45d_devclass; 432164742Simp 433164742Simpstatic device_method_t at45d_methods[] = { 434164742Simp /* Device interface */ 435164742Simp DEVMETHOD(device_probe, at45d_probe), 436164742Simp DEVMETHOD(device_attach, at45d_attach), 437164742Simp DEVMETHOD(device_detach, at45d_detach), 438164742Simp 439237384Smarius DEVMETHOD_END 440164742Simp}; 441164742Simp 442164742Simpstatic driver_t at45d_driver = { 443164742Simp "at45d", 444164742Simp at45d_methods, 445164742Simp sizeof(struct at45d_softc), 446164742Simp}; 447164742Simp 448237384SmariusDRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, NULL, NULL); 449