at45d.c revision 242625
1227825Stheraven/*- 2227825Stheraven * Copyright (c) 2006 M. Warner Losh 3227825Stheraven * Copyright (c) 2011-2012 Ian Lepore 4227825Stheraven * Copyright (c) 2012 Marius Strobl <marius@FreeBSD.org> 5227825Stheraven * All rights reserved. 6227825Stheraven * 7227825Stheraven * Redistribution and use in source and binary forms, with or without 8227825Stheraven * modification, are permitted provided that the following conditions 9227825Stheraven * are met: 10227825Stheraven * 1. Redistributions of source code must retain the above copyright 11227825Stheraven * notice, this list of conditions and the following disclaimer. 12227825Stheraven * 2. Redistributions in binary form must reproduce the above copyright 13227825Stheraven * notice, this list of conditions and the following disclaimer in the 14227825Stheraven * documentation and/or other materials provided with the distribution. 15227825Stheraven * 16227825Stheraven * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17227825Stheraven * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18227825Stheraven * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19227825Stheraven * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20227825Stheraven * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21232950Stheraven * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22232950Stheraven * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23262801Sdim * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24262801Sdim * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25262801Sdim * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26262801Sdim */ 27262801Sdim 28262801Sdim#include <sys/cdefs.h> 29227825Stheraven__FBSDID("$FreeBSD: head/sys/dev/flash/at45d.c 242625 2012-11-05 19:16:27Z dim $"); 30227825Stheraven 31227825Stheraven#include <sys/param.h> 32227825Stheraven#include <sys/systm.h> 33227825Stheraven#include <sys/bio.h> 34227825Stheraven#include <sys/bus.h> 35250514Sdim#include <sys/conf.h> 36227825Stheraven#include <sys/kernel.h> 37227825Stheraven#include <sys/kthread.h> 38227825Stheraven#include <sys/lock.h> 39227825Stheraven#include <sys/mbuf.h> 40227825Stheraven#include <sys/malloc.h> 41227825Stheraven#include <sys/module.h> 42227825Stheraven#include <sys/mutex.h> 43227825Stheraven#include <geom/geom_disk.h> 44227825Stheraven 45227825Stheraven#include <dev/spibus/spi.h> 46227825Stheraven#include "spibus_if.h" 47227825Stheraven 48227825Stheravenstruct at45d_flash_ident 49227825Stheraven{ 50227825Stheraven const char *name; 51227825Stheraven uint32_t jedec; 52227825Stheraven uint16_t pagecount; 53227825Stheraven uint16_t pageoffset; 54227825Stheraven uint16_t pagesize; 55227825Stheraven uint16_t pagesize2n; 56227825Stheraven}; 57227825Stheraven 58227825Stheravenstruct at45d_softc 59227825Stheraven{ 60227825Stheraven struct bio_queue_head bio_queue; 61227825Stheraven struct mtx sc_mtx; 62227825Stheraven struct disk *disk; 63227825Stheraven struct proc *p; 64227825Stheraven struct intr_config_hook config_intrhook; 65227825Stheraven device_t dev; 66243376Sdim uint16_t pagecount; 67243376Sdim uint16_t pageoffset; 68243376Sdim uint16_t pagesize; 69243376Sdim}; 70243376Sdim 71243376Sdim#define AT45D_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) 72243376Sdim#define AT45D_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) 73243376Sdim#define AT45D_LOCK_INIT(_sc) \ 74243376Sdim mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ 75243376Sdim "at45d", MTX_DEF) 76243376Sdim#define AT45D_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); 77243376Sdim#define AT45D_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED); 78243376Sdim#define AT45D_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED); 79243376Sdim 80243376Sdim/* bus entry points */ 81243376Sdimstatic device_attach_t at45d_attach; 82243376Sdimstatic device_detach_t at45d_detach; 83243376Sdimstatic device_probe_t at45d_probe; 84243376Sdim 85243376Sdim/* disk routines */ 86243376Sdimstatic int at45d_close(struct disk *dp); 87227825Stheravenstatic int at45d_open(struct disk *dp); 88262801Sdimstatic void at45d_strategy(struct bio *bp); 89262801Sdimstatic void at45d_task(void *arg); 90262801Sdim 91227825Stheraven/* helper routines */ 92262801Sdimstatic void at45d_delayed_attach(void *xsc); 93227825Stheravenstatic int at45d_get_mfg_info(device_t dev, uint8_t *resp); 94227825Stheravenstatic int at45d_get_status(device_t dev, uint8_t *status); 95262801Sdimstatic int at45d_wait_ready(device_t dev, uint8_t *status); 96227825Stheraven 97227825Stheraven#define BUFFER_TRANSFER 0x53 98227825Stheraven#define BUFFER_COMPARE 0x60 99227825Stheraven#define PROGRAM_THROUGH_BUFFER 0x82 100227825Stheraven#define MANUFACTURER_ID 0x9f 101227825Stheraven#define STATUS_REGISTER_READ 0xd7 102227825Stheraven#define CONTINUOUS_ARRAY_READ 0xe8 103227825Stheraven 104227825Stheraven/* 105227825Stheraven * A sectorsize2n != 0 is used to indicate that a device optionally supports 106227825Stheraven * 2^N byte pages. If support for the latter is enabled, the sector offset 107227825Stheraven * has to be reduced by one. 108227825Stheraven */ 109227825Stheravenstatic const struct at45d_flash_ident at45d_flash_devices[] = { 110227825Stheraven { "AT45DB011B", 0x1f2200, 512, 9, 264, 256 }, 111227825Stheraven { "AT45DB021B", 0x1f2300, 1024, 9, 264, 256 }, 112227825Stheraven { "AT45DB041x", 0x1f2400, 2028, 9, 264, 256 }, 113227825Stheraven { "AT45DB081B", 0x1f2500, 4096, 9, 264, 256 }, 114262801Sdim { "AT45DB161x", 0x1f2600, 4096, 10, 528, 512 }, 115262801Sdim { "AT45DB321x", 0x1f2700, 8192, 10, 528, 0 }, 116262801Sdim { "AT45DB321x", 0x1f2701, 8192, 10, 528, 512 }, 117262801Sdim { "AT45DB642x", 0x1f2800, 8192, 11, 1056, 1024 } 118262801Sdim}; 119262801Sdim 120262801Sdimstatic int 121262801Sdimat45d_get_status(device_t dev, uint8_t *status) 122262801Sdim{ 123227825Stheraven uint8_t rxBuf[8], txBuf[8]; 124262801Sdim struct spi_command cmd; 125262801Sdim int err; 126227825Stheraven 127262801Sdim memset(&cmd, 0, sizeof(cmd)); 128262801Sdim memset(txBuf, 0, sizeof(txBuf)); 129262801Sdim memset(rxBuf, 0, sizeof(rxBuf)); 130262801Sdim 131262801Sdim txBuf[0] = STATUS_REGISTER_READ; 132262801Sdim cmd.tx_cmd = txBuf; 133227825Stheraven cmd.rx_cmd = rxBuf; 134262801Sdim cmd.rx_cmd_sz = cmd.tx_cmd_sz = 2; 135262801Sdim err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd); 136262801Sdim *status = rxBuf[1]; 137262801Sdim return (err); 138227825Stheraven} 139227825Stheraven 140262801Sdimstatic int 141262801Sdimat45d_get_mfg_info(device_t dev, uint8_t *resp) 142262801Sdim{ 143262801Sdim uint8_t rxBuf[8], txBuf[8]; 144262801Sdim struct spi_command cmd; 145262801Sdim int err; 146262801Sdim 147262801Sdim memset(&cmd, 0, sizeof(cmd)); 148262801Sdim memset(txBuf, 0, sizeof(txBuf)); 149262801Sdim memset(rxBuf, 0, sizeof(rxBuf)); 150262801Sdim 151262801Sdim txBuf[0] = MANUFACTURER_ID; 152262801Sdim cmd.tx_cmd = &txBuf; 153262801Sdim cmd.rx_cmd = &rxBuf; 154262801Sdim cmd.tx_cmd_sz = cmd.rx_cmd_sz = 5; 155262801Sdim err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd); 156262801Sdim if (err) 157262801Sdim return (err); 158262801Sdim memcpy(resp, rxBuf + 1, 4); 159262801Sdim return (0); 160262801Sdim} 161262801Sdim 162262801Sdimstatic int 163262801Sdimat45d_wait_ready(device_t dev, uint8_t *status) 164262801Sdim{ 165262801Sdim struct timeval now, tout; 166262801Sdim int err; 167262801Sdim 168262801Sdim getmicrouptime(&tout); 169262801Sdim tout.tv_sec += 3; 170262801Sdim do { 171262801Sdim getmicrouptime(&now); 172227825Stheraven if (now.tv_sec > tout.tv_sec) 173227825Stheraven err = ETIMEDOUT; 174262801Sdim else 175262801Sdim err = at45d_get_status(dev, status); 176262801Sdim } while (err == 0 && (*status & 0x80) == 0); 177262801Sdim return (err); 178227825Stheraven} 179227825Stheraven 180227825Stheravenstatic int 181227825Stheravenat45d_probe(device_t dev) 182227825Stheraven{ 183227825Stheraven 184227825Stheraven device_set_desc(dev, "AT45D Flash Family"); 185227825Stheraven return (0); 186227825Stheraven} 187227825Stheraven 188227825Stheravenstatic int 189227825Stheravenat45d_attach(device_t dev) 190227825Stheraven{ 191227825Stheraven struct at45d_softc *sc; 192262801Sdim 193262801Sdim sc = device_get_softc(dev); 194262801Sdim sc->dev = dev; 195227825Stheraven AT45D_LOCK_INIT(sc); 196227825Stheraven 197262801Sdim /* We'll see what kind of flash we have later... */ 198227825Stheraven sc->config_intrhook.ich_func = at45d_delayed_attach; 199227825Stheraven sc->config_intrhook.ich_arg = sc; 200262801Sdim if (config_intrhook_establish(&sc->config_intrhook) != 0) 201227825Stheraven device_printf(dev, "config_intrhook_establish failed\n"); 202262801Sdim return (0); 203262801Sdim} 204262801Sdim 205262801Sdimstatic int 206262801Sdimat45d_detach(device_t dev) 207262801Sdim{ 208262801Sdim 209227825Stheraven return (EBUSY) /* XXX */; 210227825Stheraven} 211227825Stheraven 212262801Sdimstatic void 213227825Stheravenat45d_delayed_attach(void *xsc) 214227825Stheraven{ 215262801Sdim struct at45d_softc *sc; 216262801Sdim const struct at45d_flash_ident *ident; 217262801Sdim u_int i; 218262801Sdim uint32_t jedec; 219227825Stheraven uint16_t pagesize; 220227825Stheraven uint8_t buf[4], status; 221227825Stheraven 222262801Sdim sc = xsc; 223227825Stheraven ident = NULL; 224227825Stheraven jedec = 0; 225227825Stheraven 226227825Stheraven if (at45d_wait_ready(sc->dev, &status) != 0) 227227825Stheraven device_printf(sc->dev, "Error waiting for device-ready.\n"); 228227825Stheraven else if (at45d_get_mfg_info(sc->dev, buf) != 0) 229227825Stheraven device_printf(sc->dev, "Failed to get ID.\n"); 230227825Stheraven else { 231227825Stheraven jedec = buf[0] << 16 | buf[1] << 8 | buf[2]; 232227825Stheraven for (i = 0; i < nitems(at45d_flash_devices); i++) { 233227825Stheraven if (at45d_flash_devices[i].jedec == jedec) { 234227825Stheraven ident = &at45d_flash_devices[i]; 235227825Stheraven break; 236227825Stheraven } 237227825Stheraven } 238227825Stheraven } 239227825Stheraven if (ident == NULL) 240227825Stheraven device_printf(sc->dev, "JEDEC 0x%x not in list.\n", jedec); 241227825Stheraven else { 242227825Stheraven sc->pagecount = ident->pagecount; 243227825Stheraven sc->pageoffset = ident->pageoffset; 244227825Stheraven if (ident->pagesize2n != 0 && (status & 0x01) != 0) { 245227825Stheraven sc->pageoffset -= 1; 246227825Stheraven pagesize = ident->pagesize2n; 247227825Stheraven } else 248227825Stheraven pagesize = ident->pagesize; 249227825Stheraven sc->pagesize = pagesize; 250227825Stheraven 251227825Stheraven sc->disk = disk_alloc(); 252227825Stheraven sc->disk->d_open = at45d_open; 253262801Sdim sc->disk->d_close = at45d_close; 254262801Sdim sc->disk->d_strategy = at45d_strategy; 255262801Sdim sc->disk->d_name = "flash/spi"; 256262801Sdim sc->disk->d_drv1 = sc; 257262801Sdim sc->disk->d_maxsize = DFLTPHYS; 258262801Sdim sc->disk->d_sectorsize = pagesize; 259262801Sdim sc->disk->d_mediasize = pagesize * ident->pagecount; 260262801Sdim sc->disk->d_unit = device_get_unit(sc->dev); 261262801Sdim disk_create(sc->disk, DISK_VERSION); 262227825Stheraven bioq_init(&sc->bio_queue); 263227825Stheraven kproc_create(&at45d_task, sc, &sc->p, 0, 0, 264227825Stheraven "task: at45d flash"); 265262801Sdim device_printf(sc->dev, "%s, %d bytes per page, %d pages\n", 266262801Sdim ident->name, pagesize, ident->pagecount); 267262801Sdim } 268262801Sdim 269262801Sdim config_intrhook_disestablish(&sc->config_intrhook); 270227825Stheraven} 271262801Sdim 272262801Sdimstatic int 273227825Stheravenat45d_open(struct disk *dp) 274262801Sdim{ 275262801Sdim 276262801Sdim return (0); 277262801Sdim} 278262801Sdim 279262801Sdimstatic int 280227825Stheravenat45d_close(struct disk *dp) 281262801Sdim{ 282262801Sdim 283262801Sdim return (0); 284262801Sdim} 285227825Stheraven 286227825Stheravenstatic void 287262801Sdimat45d_strategy(struct bio *bp) 288262801Sdim{ 289262801Sdim struct at45d_softc *sc; 290262801Sdim 291262801Sdim sc = (struct at45d_softc *)bp->bio_disk->d_drv1; 292262801Sdim AT45D_LOCK(sc); 293262801Sdim bioq_disksort(&sc->bio_queue, bp); 294262801Sdim wakeup(sc); 295262801Sdim AT45D_UNLOCK(sc); 296262801Sdim} 297262801Sdim 298262801Sdimstatic void 299262801Sdimat45d_task(void *arg) 300262801Sdim{ 301262801Sdim uint8_t rxBuf[8], txBuf[8]; 302262801Sdim struct at45d_softc *sc; 303262801Sdim struct bio *bp; 304262801Sdim struct spi_command cmd; 305262801Sdim device_t dev, pdev; 306262801Sdim caddr_t buf; 307262801Sdim u_long len, resid; 308262801Sdim u_int addr, berr, err, offset, page; 309262801Sdim uint8_t status; 310262801Sdim 311262801Sdim sc = (struct at45d_softc*)arg; 312262801Sdim dev = sc->dev; 313262801Sdim pdev = device_get_parent(dev); 314262801Sdim memset(&cmd, 0, sizeof(cmd)); 315262801Sdim memset(txBuf, 0, sizeof(txBuf)); 316262801Sdim memset(rxBuf, 0, sizeof(rxBuf)); 317262801Sdim cmd.tx_cmd = txBuf; 318262801Sdim cmd.rx_cmd = rxBuf; 319227825Stheraven 320227825Stheraven for (;;) { 321262801Sdim AT45D_LOCK(sc); 322262801Sdim do { 323262801Sdim bp = bioq_takefirst(&sc->bio_queue); 324262801Sdim if (bp == NULL) 325227825Stheraven msleep(sc, &sc->sc_mtx, PRIBIO, "jobqueue", 0); 326227825Stheraven } while (bp == NULL); 327227825Stheraven AT45D_UNLOCK(sc); 328227825Stheraven 329227825Stheraven berr = 0; 330227825Stheraven buf = bp->bio_data; 331227825Stheraven len = resid = bp->bio_bcount; 332227825Stheraven page = bp->bio_offset / sc->pagesize; 333227825Stheraven offset = bp->bio_offset % sc->pagesize; 334227825Stheraven 335227825Stheraven switch (bp->bio_cmd) { 336227825Stheraven case BIO_READ: 337227825Stheraven txBuf[0] = CONTINUOUS_ARRAY_READ; 338227825Stheraven cmd.tx_cmd_sz = cmd.rx_cmd_sz = 8; 339262801Sdim cmd.tx_data = cmd.rx_data = buf; 340262801Sdim break; 341262801Sdim case BIO_WRITE: 342227825Stheraven cmd.tx_cmd_sz = cmd.rx_cmd_sz = 4; 343227825Stheraven cmd.tx_data = cmd.rx_data = buf; 344262801Sdim if (resid + offset > sc->pagesize) 345227825Stheraven len = sc->pagesize - offset; 346227825Stheraven break; 347262801Sdim default: 348227825Stheraven berr = EINVAL; 349262801Sdim goto out; 350262801Sdim } 351262801Sdim 352262801Sdim /* 353262801Sdim * NB: for BIO_READ, this loop is only traversed once. 354262801Sdim */ 355262801Sdim while (resid > 0) { 356227825Stheraven if (page > sc->pagecount) { 357227825Stheraven berr = EINVAL; 358227825Stheraven goto out; 359262801Sdim } 360227825Stheraven addr = page << sc->pageoffset; 361227825Stheraven if (bp->bio_cmd == BIO_WRITE) { 362262801Sdim if (len != sc->pagesize) { 363262801Sdim txBuf[0] = BUFFER_TRANSFER; 364262801Sdim txBuf[1] = ((addr >> 16) & 0xff); 365227825Stheraven txBuf[2] = ((addr >> 8) & 0xff); 366227825Stheraven txBuf[3] = 0; 367262801Sdim cmd.tx_data_sz = cmd.rx_data_sz = 0; 368227825Stheraven err = SPIBUS_TRANSFER(pdev, dev, 369227825Stheraven &cmd); 370262801Sdim if (err == 0) 371227825Stheraven err = at45d_wait_ready(dev, 372227825Stheraven &status); 373227825Stheraven if (err != 0) { 374227825Stheraven berr = EIO; 375227825Stheraven goto out; 376227825Stheraven } 377227825Stheraven } 378227825Stheraven txBuf[0] = PROGRAM_THROUGH_BUFFER; 379227825Stheraven } 380227825Stheraven 381227825Stheraven addr += offset; 382227825Stheraven txBuf[1] = ((addr >> 16) & 0xff); 383227825Stheraven txBuf[2] = ((addr >> 8) & 0xff); 384227825Stheraven txBuf[3] = (addr & 0xff); 385227825Stheraven cmd.tx_data_sz = cmd.rx_data_sz = len; 386227825Stheraven err = SPIBUS_TRANSFER(pdev, dev, &cmd); 387227825Stheraven if (err == 0 && bp->bio_cmd != BIO_READ) 388227825Stheraven err = at45d_wait_ready(dev, &status); 389227825Stheraven if (err != 0) { 390227825Stheraven berr = EIO; 391227825Stheraven goto out; 392262801Sdim } 393262801Sdim if (bp->bio_cmd == BIO_WRITE) { 394262801Sdim addr = page << sc->pageoffset; 395262801Sdim txBuf[0] = BUFFER_COMPARE; 396262801Sdim txBuf[1] = ((addr >> 16) & 0xff); 397262801Sdim txBuf[2] = ((addr >> 8) & 0xff); 398227825Stheraven txBuf[3] = 0; 399262801Sdim cmd.tx_data_sz = cmd.rx_data_sz = 0; 400262801Sdim err = SPIBUS_TRANSFER(pdev, dev, &cmd); 401227825Stheraven if (err == 0) 402262801Sdim err = at45d_wait_ready(dev, &status); 403262801Sdim if (err != 0 || (status & 0x40) != 0) { 404262801Sdim device_printf(dev, "comparing page " 405262801Sdim "%d failed (status=0x%x)\n", addr, 406262801Sdim status); 407262801Sdim berr = EIO; 408262801Sdim goto out; 409262801Sdim } 410227825Stheraven } 411262801Sdim page++; 412262801Sdim buf += len; 413262801Sdim offset = 0; 414262801Sdim resid -= len; 415227825Stheraven if (resid > sc->pagesize) 416227825Stheraven len = sc->pagesize; 417262801Sdim else 418262801Sdim len = resid; 419262801Sdim cmd.tx_data = cmd.rx_data = buf; 420262801Sdim } 421262801Sdim out: 422262801Sdim if (berr != 0) { 423262801Sdim bp->bio_flags |= BIO_ERROR; 424262801Sdim bp->bio_error = berr; 425262801Sdim } 426262801Sdim bp->bio_resid = resid; 427262801Sdim biodone(bp); 428262801Sdim } 429262801Sdim} 430262801Sdim 431262801Sdimstatic devclass_t at45d_devclass; 432262801Sdim 433262801Sdimstatic device_method_t at45d_methods[] = { 434262801Sdim /* Device interface */ 435262801Sdim DEVMETHOD(device_probe, at45d_probe), 436262801Sdim DEVMETHOD(device_attach, at45d_attach), 437262801Sdim DEVMETHOD(device_detach, at45d_detach), 438262801Sdim 439262801Sdim DEVMETHOD_END 440262801Sdim}; 441262801Sdim 442262801Sdimstatic driver_t at45d_driver = { 443262801Sdim "at45d", 444262801Sdim at45d_methods, 445262801Sdim sizeof(struct at45d_softc), 446262801Sdim}; 447262801Sdim 448262801SdimDRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, NULL, NULL); 449262801Sdim