1164742Simp/*- 2236496Smarius * Copyright (c) 2006 M. Warner Losh 3236496Smarius * Copyright (c) 2011-2012 Ian Lepore 4236496Smarius * Copyright (c) 2012 Marius Strobl <marius@FreeBSD.org> 5236496Smarius * 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$"); 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 48236496Smariusstruct at45d_flash_ident 49164742Simp{ 50236496Smarius const char *name; 51236496Smarius uint32_t jedec; 52236496Smarius uint16_t pagecount; 53236496Smarius uint16_t pageoffset; 54236496Smarius uint16_t pagesize; 55236496Smarius uint16_t pagesize2n; 56164742Simp}; 57164742Simp 58236496Smariusstruct at45d_softc 59236496Smarius{ 60236496Smarius struct bio_queue_head bio_queue; 61236496Smarius struct mtx sc_mtx; 62236496Smarius struct disk *disk; 63236496Smarius struct proc *p; 64236496Smarius struct intr_config_hook config_intrhook; 65236496Smarius device_t dev; 66236496Smarius uint16_t pagecount; 67236496Smarius uint16_t pageoffset; 68236496Smarius uint16_t pagesize; 69236496Smarius}; 70236496Smarius 71236496Smarius#define AT45D_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) 72164742Simp#define AT45D_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) 73236496Smarius#define AT45D_LOCK_INIT(_sc) \ 74164742Simp mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ 75164742Simp "at45d", MTX_DEF) 76236496Smarius#define AT45D_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); 77236496Smarius#define AT45D_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED); 78236496Smarius#define AT45D_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED); 79164742Simp 80236496Smarius/* bus entry points */ 81236496Smariusstatic device_attach_t at45d_attach; 82236496Smariusstatic device_detach_t at45d_detach; 83236496Smariusstatic device_probe_t at45d_probe; 84164742Simp 85164742Simp/* disk routines */ 86236496Smariusstatic 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 91236496Smarius/* helper routines */ 92236496Smariusstatic void at45d_delayed_attach(void *xsc); 93236496Smariusstatic int at45d_get_mfg_info(device_t dev, uint8_t *resp); 94236496Smariusstatic int at45d_get_status(device_t dev, uint8_t *status); 95236496Smariusstatic int at45d_wait_ready(device_t dev, uint8_t *status); 96164742Simp 97236496Smarius#define BUFFER_TRANSFER 0x53 98236496Smarius#define BUFFER_COMPARE 0x60 99236496Smarius#define PROGRAM_THROUGH_BUFFER 0x82 100236496Smarius#define MANUFACTURER_ID 0x9f 101236496Smarius#define STATUS_REGISTER_READ 0xd7 102236496Smarius#define CONTINUOUS_ARRAY_READ 0xe8 103236496Smarius 104236496Smarius/* 105236496Smarius * A sectorsize2n != 0 is used to indicate that a device optionally supports 106236496Smarius * 2^N byte pages. If support for the latter is enabled, the sector offset 107236496Smarius * has to be reduced by one. 108236496Smarius */ 109242625Sdimstatic const struct at45d_flash_ident at45d_flash_devices[] = { 110236496Smarius { "AT45DB011B", 0x1f2200, 512, 9, 264, 256 }, 111236496Smarius { "AT45DB021B", 0x1f2300, 1024, 9, 264, 256 }, 112236496Smarius { "AT45DB041x", 0x1f2400, 2028, 9, 264, 256 }, 113236496Smarius { "AT45DB081B", 0x1f2500, 4096, 9, 264, 256 }, 114236496Smarius { "AT45DB161x", 0x1f2600, 4096, 10, 528, 512 }, 115236496Smarius { "AT45DB321x", 0x1f2700, 8192, 10, 528, 0 }, 116236496Smarius { "AT45DB321x", 0x1f2701, 8192, 10, 528, 512 }, 117236496Smarius { "AT45DB642x", 0x1f2800, 8192, 11, 1056, 1024 } 118236496Smarius}; 119236496Smarius 120236496Smariusstatic int 121236496Smariusat45d_get_status(device_t dev, uint8_t *status) 122164742Simp{ 123236496Smarius 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; 134236496Smarius cmd.rx_cmd_sz = cmd.tx_cmd_sz = 2; 135164742Simp err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd); 136236496Smarius *status = rxBuf[1]; 137236496Smarius return (err); 138164742Simp} 139164742Simp 140164742Simpstatic int 141164742Simpat45d_get_mfg_info(device_t dev, uint8_t *resp) 142164742Simp{ 143236496Smarius 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; 154236496Smarius 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 163236496Smariusat45d_wait_ready(device_t dev, uint8_t *status) 164236496Smarius{ 165236496Smarius struct timeval now, tout; 166236496Smarius int err; 167236496Smarius 168236496Smarius getmicrouptime(&tout); 169236496Smarius tout.tv_sec += 3; 170236496Smarius do { 171236496Smarius getmicrouptime(&now); 172236496Smarius if (now.tv_sec > tout.tv_sec) 173236496Smarius err = ETIMEDOUT; 174236496Smarius else 175236496Smarius err = at45d_get_status(dev, status); 176236496Smarius } while (err == 0 && (*status & 0x80) == 0); 177236496Smarius return (err); 178236496Smarius} 179236496Smarius 180236496Smariusstatic int 181164742Simpat45d_probe(device_t dev) 182164742Simp{ 183236496Smarius 184236496Smarius 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{ 208236496Smarius 209236496Smarius return (EBUSY) /* XXX */; 210164742Simp} 211164742Simp 212164742Simpstatic void 213164742Simpat45d_delayed_attach(void *xsc) 214164742Simp{ 215236496Smarius struct at45d_softc *sc; 216236496Smarius const struct at45d_flash_ident *ident; 217236496Smarius u_int i; 218236496Smarius uint32_t jedec; 219236496Smarius uint16_t pagesize; 220236496Smarius uint8_t buf[4], status; 221164742Simp 222236496Smarius sc = xsc; 223236496Smarius ident = NULL; 224236496Smarius jedec = 0; 225164742Simp 226236496Smarius if (at45d_wait_ready(sc->dev, &status) != 0) 227236496Smarius device_printf(sc->dev, "Error waiting for device-ready.\n"); 228236496Smarius else if (at45d_get_mfg_info(sc->dev, buf) != 0) 229236496Smarius device_printf(sc->dev, "Failed to get ID.\n"); 230236496Smarius else { 231236496Smarius jedec = buf[0] << 16 | buf[1] << 8 | buf[2]; 232236496Smarius for (i = 0; i < nitems(at45d_flash_devices); i++) { 233236496Smarius if (at45d_flash_devices[i].jedec == jedec) { 234236496Smarius ident = &at45d_flash_devices[i]; 235236496Smarius break; 236236496Smarius } 237236496Smarius } 238236496Smarius } 239236496Smarius if (ident == NULL) 240236496Smarius device_printf(sc->dev, "JEDEC 0x%x not in list.\n", jedec); 241236496Smarius else { 242236496Smarius sc->pagecount = ident->pagecount; 243236496Smarius sc->pageoffset = ident->pageoffset; 244236496Smarius if (ident->pagesize2n != 0 && (status & 0x01) != 0) { 245236496Smarius sc->pageoffset -= 1; 246236496Smarius pagesize = ident->pagesize2n; 247236496Smarius } else 248236496Smarius pagesize = ident->pagesize; 249236496Smarius sc->pagesize = pagesize; 250236496Smarius 251236496Smarius sc->disk = disk_alloc(); 252236496Smarius sc->disk->d_open = at45d_open; 253236496Smarius sc->disk->d_close = at45d_close; 254236496Smarius sc->disk->d_strategy = at45d_strategy; 255236496Smarius sc->disk->d_name = "flash/spi"; 256236496Smarius sc->disk->d_drv1 = sc; 257236496Smarius sc->disk->d_maxsize = DFLTPHYS; 258236496Smarius sc->disk->d_sectorsize = pagesize; 259236496Smarius sc->disk->d_mediasize = pagesize * ident->pagecount; 260236496Smarius sc->disk->d_unit = device_get_unit(sc->dev); 261236496Smarius disk_create(sc->disk, DISK_VERSION); 262236496Smarius bioq_init(&sc->bio_queue); 263236496Smarius kproc_create(&at45d_task, sc, &sc->p, 0, 0, 264236496Smarius "task: at45d flash"); 265236496Smarius device_printf(sc->dev, "%s, %d bytes per page, %d pages\n", 266236496Smarius ident->name, pagesize, ident->pagecount); 267236496Smarius } 268236496Smarius 269164742Simp config_intrhook_disestablish(&sc->config_intrhook); 270164742Simp} 271164742Simp 272164742Simpstatic int 273164742Simpat45d_open(struct disk *dp) 274164742Simp{ 275236496Smarius 276236496Smarius return (0); 277164742Simp} 278164742Simp 279164742Simpstatic int 280164742Simpat45d_close(struct disk *dp) 281164742Simp{ 282236496Smarius 283236496Smarius 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{ 301236496Smarius uint8_t rxBuf[8], txBuf[8]; 302236496Smarius struct at45d_softc *sc; 303164742Simp struct bio *bp; 304164742Simp struct spi_command cmd; 305164742Simp device_t dev, pdev; 306236496Smarius caddr_t buf; 307236496Smarius u_long len, resid; 308236496Smarius u_int addr, berr, err, offset, page; 309236496Smarius uint8_t status; 310164742Simp 311236496Smarius sc = (struct at45d_softc*)arg; 312236496Smarius dev = sc->dev; 313236496Smarius pdev = device_get_parent(dev); 314236496Smarius memset(&cmd, 0, sizeof(cmd)); 315236496Smarius memset(txBuf, 0, sizeof(txBuf)); 316236496Smarius memset(rxBuf, 0, sizeof(rxBuf)); 317236496Smarius cmd.tx_cmd = txBuf; 318236496Smarius cmd.rx_cmd = rxBuf; 319236496Smarius 320164742Simp for (;;) { 321164742Simp AT45D_LOCK(sc); 322164742Simp do { 323236496Smarius 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); 328236496Smarius 329236496Smarius berr = 0; 330236496Smarius buf = bp->bio_data; 331236496Smarius len = resid = bp->bio_bcount; 332236496Smarius page = bp->bio_offset / sc->pagesize; 333236496Smarius offset = bp->bio_offset % sc->pagesize; 334236496Smarius 335236496Smarius switch (bp->bio_cmd) { 336236496Smarius case BIO_READ: 337236496Smarius txBuf[0] = CONTINUOUS_ARRAY_READ; 338236496Smarius cmd.tx_cmd_sz = cmd.rx_cmd_sz = 8; 339236496Smarius cmd.tx_data = cmd.rx_data = buf; 340236496Smarius break; 341236496Smarius case BIO_WRITE: 342236496Smarius cmd.tx_cmd_sz = cmd.rx_cmd_sz = 4; 343236496Smarius cmd.tx_data = cmd.rx_data = buf; 344236496Smarius if (resid + offset > sc->pagesize) 345236496Smarius len = sc->pagesize - offset; 346236496Smarius break; 347236496Smarius default: 348236496Smarius berr = EINVAL; 349236496Smarius goto out; 350236496Smarius } 351236496Smarius 352236496Smarius /* 353236496Smarius * NB: for BIO_READ, this loop is only traversed once. 354236496Smarius */ 355236496Smarius while (resid > 0) { 356236496Smarius if (page > sc->pagecount) { 357236496Smarius berr = EINVAL; 358236496Smarius goto out; 359236496Smarius } 360236496Smarius addr = page << sc->pageoffset; 361236496Smarius if (bp->bio_cmd == BIO_WRITE) { 362236496Smarius if (len != sc->pagesize) { 363236496Smarius txBuf[0] = BUFFER_TRANSFER; 364236496Smarius txBuf[1] = ((addr >> 16) & 0xff); 365236496Smarius txBuf[2] = ((addr >> 8) & 0xff); 366236496Smarius txBuf[3] = 0; 367236496Smarius cmd.tx_data_sz = cmd.rx_data_sz = 0; 368236496Smarius err = SPIBUS_TRANSFER(pdev, dev, 369236496Smarius &cmd); 370236496Smarius if (err == 0) 371236496Smarius err = at45d_wait_ready(dev, 372236496Smarius &status); 373236496Smarius if (err != 0) { 374236496Smarius berr = EIO; 375236496Smarius goto out; 376236496Smarius } 377236496Smarius } 378164742Simp txBuf[0] = PROGRAM_THROUGH_BUFFER; 379164742Simp } 380236496Smarius 381236496Smarius addr += offset; 382236496Smarius txBuf[1] = ((addr >> 16) & 0xff); 383236496Smarius txBuf[2] = ((addr >> 8) & 0xff); 384236496Smarius txBuf[3] = (addr & 0xff); 385236496Smarius cmd.tx_data_sz = cmd.rx_data_sz = len; 386164742Simp err = SPIBUS_TRANSFER(pdev, dev, &cmd); 387236496Smarius if (err == 0 && bp->bio_cmd != BIO_READ) 388236496Smarius err = at45d_wait_ready(dev, &status); 389236496Smarius if (err != 0) { 390236496Smarius berr = EIO; 391236496Smarius goto out; 392236496Smarius } 393236496Smarius if (bp->bio_cmd == BIO_WRITE) { 394236496Smarius addr = page << sc->pageoffset; 395236496Smarius txBuf[0] = BUFFER_COMPARE; 396236496Smarius txBuf[1] = ((addr >> 16) & 0xff); 397236496Smarius txBuf[2] = ((addr >> 8) & 0xff); 398236496Smarius txBuf[3] = 0; 399236496Smarius cmd.tx_data_sz = cmd.rx_data_sz = 0; 400236496Smarius err = SPIBUS_TRANSFER(pdev, dev, &cmd); 401236496Smarius if (err == 0) 402236496Smarius err = at45d_wait_ready(dev, &status); 403236496Smarius if (err != 0 || (status & 0x40) != 0) { 404236496Smarius device_printf(dev, "comparing page " 405236496Smarius "%d failed (status=0x%x)\n", addr, 406236496Smarius status); 407236496Smarius berr = EIO; 408236496Smarius goto out; 409236496Smarius } 410236496Smarius } 411236496Smarius page++; 412236496Smarius buf += len; 413236496Smarius offset = 0; 414236496Smarius resid -= len; 415236496Smarius if (resid > sc->pagesize) 416236496Smarius len = sc->pagesize; 417236496Smarius else 418236496Smarius len = resid; 419236496Smarius cmd.tx_data = cmd.rx_data = buf; 420164742Simp } 421236496Smarius out: 422236496Smarius if (berr != 0) { 423236496Smarius bp->bio_flags |= BIO_ERROR; 424236496Smarius bp->bio_error = berr; 425236496Smarius } 426236496Smarius 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 439236496Smarius DEVMETHOD_END 440164742Simp}; 441164742Simp 442164742Simpstatic driver_t at45d_driver = { 443164742Simp "at45d", 444164742Simp at45d_methods, 445164742Simp sizeof(struct at45d_softc), 446164742Simp}; 447164742Simp 448236496SmariusDRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, NULL, NULL); 449