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: stable/11/sys/dev/flash/at45d.c 346557 2019-04-22 15:04:11Z ian $"); 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> 36346557Sian#include <sys/endian.h> 37164742Simp#include <sys/kernel.h> 38164742Simp#include <sys/kthread.h> 39164742Simp#include <sys/lock.h> 40164742Simp#include <sys/mbuf.h> 41164742Simp#include <sys/malloc.h> 42164742Simp#include <sys/module.h> 43164742Simp#include <sys/mutex.h> 44164742Simp#include <geom/geom_disk.h> 45164742Simp 46164742Simp#include <dev/spibus/spi.h> 47164742Simp#include "spibus_if.h" 48164742Simp 49346557Sian#include "opt_platform.h" 50346557Sian 51346557Sian#ifdef FDT 52346557Sian#include <dev/fdt/fdt_common.h> 53346557Sian#include <dev/ofw/ofw_bus_subr.h> 54346557Sian#include <dev/ofw/openfirm.h> 55346557Sian 56346557Sianstatic struct ofw_compat_data compat_data[] = { 57346557Sian { "atmel,at45", 1 }, 58346557Sian { "atmel,dataflash", 1 }, 59346557Sian { NULL, 0 }, 60346557Sian}; 61346557Sian#endif 62346557Sian 63346557Sian/* This is the information returned by the MANUFACTURER_ID command. */ 64346557Sianstruct at45d_mfg_info { 65346557Sian uint32_t jedec_id; /* Mfg ID, DevId1, DevId2, ExtLen */ 66346557Sian uint16_t ext_id; /* ExtId1, ExtId2 */ 67346557Sian}; 68346557Sian 69346557Sian/* 70346557Sian * This is an entry in our table of metadata describing the chips. We match on 71346557Sian * both jedec id and extended id info returned by the MANUFACTURER_ID command. 72346557Sian */ 73236496Smariusstruct at45d_flash_ident 74164742Simp{ 75236496Smarius const char *name; 76236496Smarius uint32_t jedec; 77346557Sian uint16_t extid; 78346557Sian uint16_t extmask; 79236496Smarius uint16_t pagecount; 80236496Smarius uint16_t pageoffset; 81236496Smarius uint16_t pagesize; 82236496Smarius uint16_t pagesize2n; 83164742Simp}; 84164742Simp 85236496Smariusstruct at45d_softc 86236496Smarius{ 87236496Smarius struct bio_queue_head bio_queue; 88236496Smarius struct mtx sc_mtx; 89236496Smarius struct disk *disk; 90236496Smarius struct proc *p; 91236496Smarius device_t dev; 92346557Sian u_int taskstate; 93236496Smarius uint16_t pagecount; 94236496Smarius uint16_t pageoffset; 95236496Smarius uint16_t pagesize; 96346557Sian void *dummybuf; 97236496Smarius}; 98236496Smarius 99346557Sian#define TSTATE_STOPPED 0 100346557Sian#define TSTATE_STOPPING 1 101346557Sian#define TSTATE_RUNNING 2 102346557Sian 103236496Smarius#define AT45D_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) 104164742Simp#define AT45D_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) 105236496Smarius#define AT45D_LOCK_INIT(_sc) \ 106164742Simp mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ 107164742Simp "at45d", MTX_DEF) 108236496Smarius#define AT45D_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); 109236496Smarius#define AT45D_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED); 110236496Smarius#define AT45D_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED); 111164742Simp 112236496Smarius/* bus entry points */ 113236496Smariusstatic device_attach_t at45d_attach; 114236496Smariusstatic device_detach_t at45d_detach; 115236496Smariusstatic device_probe_t at45d_probe; 116164742Simp 117164742Simp/* disk routines */ 118236496Smariusstatic int at45d_close(struct disk *dp); 119164742Simpstatic int at45d_open(struct disk *dp); 120346557Sianstatic int at45d_getattr(struct bio *bp); 121164742Simpstatic void at45d_strategy(struct bio *bp); 122164742Simpstatic void at45d_task(void *arg); 123164742Simp 124236496Smarius/* helper routines */ 125236496Smariusstatic void at45d_delayed_attach(void *xsc); 126346557Sianstatic int at45d_get_mfg_info(device_t dev, struct at45d_mfg_info *resp); 127236496Smariusstatic int at45d_get_status(device_t dev, uint8_t *status); 128236496Smariusstatic int at45d_wait_ready(device_t dev, uint8_t *status); 129164742Simp 130346557Sian#define PAGE_TO_BUFFER_TRANSFER 0x53 131346557Sian#define PAGE_TO_BUFFER_COMPARE 0x60 132236496Smarius#define PROGRAM_THROUGH_BUFFER 0x82 133236496Smarius#define MANUFACTURER_ID 0x9f 134236496Smarius#define STATUS_REGISTER_READ 0xd7 135236496Smarius#define CONTINUOUS_ARRAY_READ 0xe8 136236496Smarius 137346557Sian#define STATUS_READY (1u << 7) 138346557Sian#define STATUS_CMPFAIL (1u << 6) 139346557Sian#define STATUS_PAGE2N (1u << 0) 140346557Sian 141236496Smarius/* 142346557Sian * Metadata for supported chips. 143346557Sian * 144346557Sian * The jedec id in this table includes the extended id length byte. A match is 145346557Sian * based on both jedec id and extended id matching. The chip's extended id (not 146346557Sian * present in most chips) is ANDed with ExtMask and the result is compared to 147346557Sian * ExtId. If a chip only returns 1 ext id byte it will be in the upper 8 bits 148346557Sian * of ExtId in this table. 149346557Sian * 150236496Smarius * A sectorsize2n != 0 is used to indicate that a device optionally supports 151236496Smarius * 2^N byte pages. If support for the latter is enabled, the sector offset 152236496Smarius * has to be reduced by one. 153236496Smarius */ 154242625Sdimstatic const struct at45d_flash_ident at45d_flash_devices[] = { 155346557Sian /* Part Name Jedec ID ExtId ExtMask PgCnt Offs PgSz PgSz2n */ 156346557Sian { "AT45DB011B", 0x1f220000, 0x0000, 0x0000, 512, 9, 264, 256 }, 157346557Sian { "AT45DB021B", 0x1f230000, 0x0000, 0x0000, 1024, 9, 264, 256 }, 158346557Sian { "AT45DB041x", 0x1f240000, 0x0000, 0x0000, 2028, 9, 264, 256 }, 159346557Sian { "AT45DB081B", 0x1f250000, 0x0000, 0x0000, 4096, 9, 264, 256 }, 160346557Sian { "AT45DB161x", 0x1f260000, 0x0000, 0x0000, 4096, 10, 528, 512 }, 161346557Sian { "AT45DB321x", 0x1f270000, 0x0000, 0x0000, 8192, 10, 528, 0 }, 162346557Sian { "AT45DB321x", 0x1f270100, 0x0000, 0x0000, 8192, 10, 528, 512 }, 163346557Sian { "AT45DB641E", 0x1f280001, 0x0000, 0xff00, 32768, 9, 264, 256 }, 164346557Sian { "AT45DB642x", 0x1f280000, 0x0000, 0x0000, 8192, 11, 1056, 1024 }, 165236496Smarius}; 166236496Smarius 167236496Smariusstatic int 168236496Smariusat45d_get_status(device_t dev, uint8_t *status) 169164742Simp{ 170236496Smarius uint8_t rxBuf[8], txBuf[8]; 171298148Sadrian struct spi_command cmd; 172164742Simp int err; 173164742Simp 174164742Simp memset(&cmd, 0, sizeof(cmd)); 175164742Simp memset(txBuf, 0, sizeof(txBuf)); 176164742Simp memset(rxBuf, 0, sizeof(rxBuf)); 177164742Simp 178164742Simp txBuf[0] = STATUS_REGISTER_READ; 179164742Simp cmd.tx_cmd = txBuf; 180164742Simp cmd.rx_cmd = rxBuf; 181236496Smarius cmd.rx_cmd_sz = cmd.tx_cmd_sz = 2; 182164742Simp err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd); 183236496Smarius *status = rxBuf[1]; 184236496Smarius return (err); 185164742Simp} 186164742Simp 187164742Simpstatic int 188346557Sianat45d_get_mfg_info(device_t dev, struct at45d_mfg_info *resp) 189164742Simp{ 190236496Smarius uint8_t rxBuf[8], txBuf[8]; 191298148Sadrian struct spi_command cmd; 192164742Simp int err; 193164742Simp 194164742Simp memset(&cmd, 0, sizeof(cmd)); 195164742Simp memset(txBuf, 0, sizeof(txBuf)); 196164742Simp memset(rxBuf, 0, sizeof(rxBuf)); 197164742Simp 198164742Simp txBuf[0] = MANUFACTURER_ID; 199164742Simp cmd.tx_cmd = &txBuf; 200164742Simp cmd.rx_cmd = &rxBuf; 201346557Sian cmd.tx_cmd_sz = cmd.rx_cmd_sz = 7; 202164742Simp err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd); 203164742Simp if (err) 204164742Simp return (err); 205346557Sian 206346557Sian resp->jedec_id = be32dec(rxBuf + 1); 207346557Sian resp->ext_id = be16dec(rxBuf + 5); 208346557Sian 209164742Simp return (0); 210164742Simp} 211164742Simp 212164742Simpstatic int 213236496Smariusat45d_wait_ready(device_t dev, uint8_t *status) 214236496Smarius{ 215236496Smarius struct timeval now, tout; 216236496Smarius int err; 217236496Smarius 218236496Smarius getmicrouptime(&tout); 219236496Smarius tout.tv_sec += 3; 220236496Smarius do { 221236496Smarius getmicrouptime(&now); 222236496Smarius if (now.tv_sec > tout.tv_sec) 223236496Smarius err = ETIMEDOUT; 224236496Smarius else 225236496Smarius err = at45d_get_status(dev, status); 226346557Sian } while (err == 0 && !(*status & STATUS_READY)); 227236496Smarius return (err); 228236496Smarius} 229236496Smarius 230236496Smariusstatic int 231164742Simpat45d_probe(device_t dev) 232164742Simp{ 233346557Sian int rv; 234236496Smarius 235346557Sian#ifdef FDT 236346557Sian if (!ofw_bus_status_okay(dev)) 237346557Sian return (ENXIO); 238346557Sian 239346557Sian if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) 240346557Sian return (ENXIO); 241346557Sian 242346557Sian rv = BUS_PROBE_DEFAULT; 243346557Sian#else 244346557Sian rv = BUS_PROBE_NOWILDCARD; 245346557Sian#endif 246346557Sian 247236496Smarius device_set_desc(dev, "AT45D Flash Family"); 248346557Sian return (rv); 249164742Simp} 250164742Simp 251164742Simpstatic int 252164742Simpat45d_attach(device_t dev) 253164742Simp{ 254164742Simp struct at45d_softc *sc; 255164742Simp 256164742Simp sc = device_get_softc(dev); 257164742Simp sc->dev = dev; 258164742Simp AT45D_LOCK_INIT(sc); 259164742Simp 260346557Sian config_intrhook_oneshot(at45d_delayed_attach, sc); 261164742Simp return (0); 262164742Simp} 263164742Simp 264164742Simpstatic int 265164742Simpat45d_detach(device_t dev) 266164742Simp{ 267346557Sian struct at45d_softc *sc; 268346557Sian int err; 269236496Smarius 270346557Sian sc = device_get_softc(dev); 271346557Sian err = 0; 272346557Sian 273346557Sian AT45D_LOCK(sc); 274346557Sian if (sc->taskstate == TSTATE_RUNNING) { 275346557Sian sc->taskstate = TSTATE_STOPPING; 276346557Sian wakeup(sc); 277346557Sian while (err == 0 && sc->taskstate != TSTATE_STOPPED) { 278346557Sian err = msleep(sc, &sc->sc_mtx, 0, "at45dt", hz * 3); 279346557Sian if (err != 0) { 280346557Sian sc->taskstate = TSTATE_RUNNING; 281346557Sian device_printf(sc->dev, 282346557Sian "Failed to stop queue task\n"); 283346557Sian } 284346557Sian } 285346557Sian } 286346557Sian AT45D_UNLOCK(sc); 287346557Sian 288346557Sian if (err == 0 && sc->taskstate == TSTATE_STOPPED) { 289346557Sian if (sc->disk) { 290346557Sian disk_destroy(sc->disk); 291346557Sian bioq_flush(&sc->bio_queue, NULL, ENXIO); 292346557Sian free(sc->dummybuf, M_DEVBUF); 293346557Sian } 294346557Sian AT45D_LOCK_DESTROY(sc); 295346557Sian } 296346557Sian return (err); 297164742Simp} 298164742Simp 299164742Simpstatic void 300164742Simpat45d_delayed_attach(void *xsc) 301164742Simp{ 302236496Smarius struct at45d_softc *sc; 303346557Sian struct at45d_mfg_info mfginfo; 304236496Smarius const struct at45d_flash_ident *ident; 305236496Smarius u_int i; 306346557Sian int sectorsize; 307236496Smarius uint32_t jedec; 308236496Smarius uint16_t pagesize; 309346557Sian uint8_t status; 310164742Simp 311236496Smarius sc = xsc; 312236496Smarius ident = NULL; 313236496Smarius jedec = 0; 314164742Simp 315346557Sian if (at45d_wait_ready(sc->dev, &status) != 0) { 316236496Smarius device_printf(sc->dev, "Error waiting for device-ready.\n"); 317346557Sian return; 318346557Sian } 319346557Sian if (at45d_get_mfg_info(sc->dev, &mfginfo) != 0) { 320236496Smarius device_printf(sc->dev, "Failed to get ID.\n"); 321346557Sian return; 322346557Sian } 323346557Sian for (i = 0; i < nitems(at45d_flash_devices); i++) { 324346557Sian ident = &at45d_flash_devices[i]; 325346557Sian if (mfginfo.jedec_id == ident->jedec && 326346557Sian (mfginfo.ext_id & ident->extmask) == ident->extid) { 327346557Sian break; 328236496Smarius } 329236496Smarius } 330346557Sian if (i == nitems(at45d_flash_devices)) { 331236496Smarius device_printf(sc->dev, "JEDEC 0x%x not in list.\n", jedec); 332346557Sian return; 333346557Sian } 334236496Smarius 335346557Sian sc->pagecount = ident->pagecount; 336346557Sian sc->pageoffset = ident->pageoffset; 337346557Sian if (ident->pagesize2n != 0 && (status & STATUS_PAGE2N)) { 338346557Sian sc->pageoffset -= 1; 339346557Sian pagesize = ident->pagesize2n; 340346557Sian } else 341346557Sian pagesize = ident->pagesize; 342346557Sian sc->pagesize = pagesize; 343346557Sian 344346557Sian /* 345346557Sian * By default we set up a disk with a sector size that matches the 346346557Sian * device page size. If there is a device hint or fdt property 347346557Sian * requesting a different size, use that, as long as it is a multiple of 348346557Sian * the device page size). 349346557Sian */ 350346557Sian sectorsize = pagesize; 351346557Sian#ifdef FDT 352346557Sian { 353346557Sian pcell_t size; 354346557Sian if (OF_getencprop(ofw_bus_get_node(sc->dev), 355346557Sian "freebsd,sectorsize", &size, sizeof(size)) > 0) 356346557Sian sectorsize = size; 357236496Smarius } 358346557Sian#endif 359346557Sian resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev), 360346557Sian "sectorsize", §orsize); 361236496Smarius 362346557Sian if ((sectorsize % pagesize) != 0) { 363346557Sian device_printf(sc->dev, "Invalid sectorsize %d, " 364346557Sian "must be a multiple of %d\n", sectorsize, pagesize); 365346557Sian return; 366346557Sian } 367346557Sian 368346557Sian sc->dummybuf = malloc(pagesize, M_DEVBUF, M_WAITOK | M_ZERO); 369346557Sian 370346557Sian sc->disk = disk_alloc(); 371346557Sian sc->disk->d_open = at45d_open; 372346557Sian sc->disk->d_close = at45d_close; 373346557Sian sc->disk->d_strategy = at45d_strategy; 374346557Sian sc->disk->d_getattr = at45d_getattr; 375346557Sian sc->disk->d_name = "flash/at45d"; 376346557Sian sc->disk->d_drv1 = sc; 377346557Sian sc->disk->d_maxsize = DFLTPHYS; 378346557Sian sc->disk->d_sectorsize = sectorsize; 379346557Sian sc->disk->d_mediasize = pagesize * ident->pagecount; 380346557Sian sc->disk->d_unit = device_get_unit(sc->dev); 381346557Sian disk_create(sc->disk, DISK_VERSION); 382346557Sian bioq_init(&sc->bio_queue); 383346557Sian kproc_create(&at45d_task, sc, &sc->p, 0, 0, "task: at45d flash"); 384346557Sian sc->taskstate = TSTATE_RUNNING; 385346557Sian device_printf(sc->dev, 386346557Sian "%s, %d bytes per page, %d pages; %d KBytes; disk sector size %d\n", 387346557Sian ident->name, pagesize, ident->pagecount, 388346557Sian (pagesize * ident->pagecount) / 1024, sectorsize); 389164742Simp} 390164742Simp 391164742Simpstatic int 392164742Simpat45d_open(struct disk *dp) 393164742Simp{ 394236496Smarius 395236496Smarius return (0); 396164742Simp} 397164742Simp 398164742Simpstatic int 399164742Simpat45d_close(struct disk *dp) 400164742Simp{ 401236496Smarius 402236496Smarius return (0); 403164742Simp} 404164742Simp 405346557Sianstatic int 406346557Sianat45d_getattr(struct bio *bp) 407346557Sian{ 408346557Sian struct at45d_softc *sc; 409346557Sian 410346557Sian /* 411346557Sian * This function exists to support geom_flashmap and fdt_slicer. 412346557Sian */ 413346557Sian 414346557Sian if (bp->bio_disk == NULL || bp->bio_disk->d_drv1 == NULL) 415346557Sian return (ENXIO); 416346557Sian if (strcmp(bp->bio_attribute, "SPI::device") != 0) 417346557Sian return (-1); 418346557Sian sc = bp->bio_disk->d_drv1; 419346557Sian if (bp->bio_length != sizeof(sc->dev)) 420346557Sian return (EFAULT); 421346557Sian bcopy(&sc->dev, bp->bio_data, sizeof(sc->dev)); 422346557Sian return (0); 423346557Sian} 424346557Sian 425164742Simpstatic void 426164742Simpat45d_strategy(struct bio *bp) 427164742Simp{ 428164742Simp struct at45d_softc *sc; 429164742Simp 430164742Simp sc = (struct at45d_softc *)bp->bio_disk->d_drv1; 431164742Simp AT45D_LOCK(sc); 432164742Simp bioq_disksort(&sc->bio_queue, bp); 433164742Simp wakeup(sc); 434164742Simp AT45D_UNLOCK(sc); 435164742Simp} 436164742Simp 437164742Simpstatic void 438164742Simpat45d_task(void *arg) 439164742Simp{ 440236496Smarius uint8_t rxBuf[8], txBuf[8]; 441236496Smarius struct at45d_softc *sc; 442164742Simp struct bio *bp; 443298148Sadrian struct spi_command cmd; 444164742Simp device_t dev, pdev; 445236496Smarius caddr_t buf; 446236496Smarius u_long len, resid; 447236496Smarius u_int addr, berr, err, offset, page; 448236496Smarius uint8_t status; 449164742Simp 450236496Smarius sc = (struct at45d_softc*)arg; 451236496Smarius dev = sc->dev; 452236496Smarius pdev = device_get_parent(dev); 453236496Smarius memset(&cmd, 0, sizeof(cmd)); 454236496Smarius memset(txBuf, 0, sizeof(txBuf)); 455236496Smarius memset(rxBuf, 0, sizeof(rxBuf)); 456236496Smarius cmd.tx_cmd = txBuf; 457236496Smarius cmd.rx_cmd = rxBuf; 458236496Smarius 459164742Simp for (;;) { 460164742Simp AT45D_LOCK(sc); 461164742Simp do { 462346557Sian if (sc->taskstate == TSTATE_STOPPING) { 463346557Sian sc->taskstate = TSTATE_STOPPED; 464346557Sian AT45D_UNLOCK(sc); 465346557Sian wakeup(sc); 466346557Sian kproc_exit(0); 467346557Sian } 468236496Smarius bp = bioq_takefirst(&sc->bio_queue); 469164742Simp if (bp == NULL) 470346557Sian msleep(sc, &sc->sc_mtx, PRIBIO, "at45dq", 0); 471164742Simp } while (bp == NULL); 472164742Simp AT45D_UNLOCK(sc); 473236496Smarius 474236496Smarius berr = 0; 475236496Smarius buf = bp->bio_data; 476236496Smarius len = resid = bp->bio_bcount; 477236496Smarius page = bp->bio_offset / sc->pagesize; 478236496Smarius offset = bp->bio_offset % sc->pagesize; 479236496Smarius 480236496Smarius switch (bp->bio_cmd) { 481236496Smarius case BIO_READ: 482236496Smarius txBuf[0] = CONTINUOUS_ARRAY_READ; 483236496Smarius cmd.tx_cmd_sz = cmd.rx_cmd_sz = 8; 484346557Sian cmd.tx_data = sc->dummybuf; 485346557Sian cmd.rx_data = buf; 486236496Smarius break; 487236496Smarius case BIO_WRITE: 488236496Smarius cmd.tx_cmd_sz = cmd.rx_cmd_sz = 4; 489346557Sian cmd.tx_data = buf; 490346557Sian cmd.rx_data = sc->dummybuf; 491236496Smarius if (resid + offset > sc->pagesize) 492236496Smarius len = sc->pagesize - offset; 493236496Smarius break; 494236496Smarius default: 495236496Smarius berr = EINVAL; 496236496Smarius goto out; 497236496Smarius } 498236496Smarius 499236496Smarius /* 500236496Smarius * NB: for BIO_READ, this loop is only traversed once. 501236496Smarius */ 502236496Smarius while (resid > 0) { 503236496Smarius if (page > sc->pagecount) { 504236496Smarius berr = EINVAL; 505236496Smarius goto out; 506236496Smarius } 507236496Smarius addr = page << sc->pageoffset; 508236496Smarius if (bp->bio_cmd == BIO_WRITE) { 509346557Sian /* 510346557Sian * If writing less than a full page, transfer 511346557Sian * the existing page to the buffer, so that our 512346557Sian * PROGRAM_THROUGH_BUFFER below will preserve 513346557Sian * the parts of the page we're not writing. 514346557Sian */ 515236496Smarius if (len != sc->pagesize) { 516346557Sian txBuf[0] = PAGE_TO_BUFFER_TRANSFER; 517236496Smarius txBuf[1] = ((addr >> 16) & 0xff); 518236496Smarius txBuf[2] = ((addr >> 8) & 0xff); 519236496Smarius txBuf[3] = 0; 520236496Smarius cmd.tx_data_sz = cmd.rx_data_sz = 0; 521346557Sian err = SPIBUS_TRANSFER(pdev, dev, &cmd); 522236496Smarius if (err == 0) 523236496Smarius err = at45d_wait_ready(dev, 524236496Smarius &status); 525236496Smarius if (err != 0) { 526236496Smarius berr = EIO; 527236496Smarius goto out; 528236496Smarius } 529236496Smarius } 530164742Simp txBuf[0] = PROGRAM_THROUGH_BUFFER; 531164742Simp } 532236496Smarius 533236496Smarius addr += offset; 534236496Smarius txBuf[1] = ((addr >> 16) & 0xff); 535236496Smarius txBuf[2] = ((addr >> 8) & 0xff); 536236496Smarius txBuf[3] = (addr & 0xff); 537236496Smarius cmd.tx_data_sz = cmd.rx_data_sz = len; 538164742Simp err = SPIBUS_TRANSFER(pdev, dev, &cmd); 539236496Smarius if (err == 0 && bp->bio_cmd != BIO_READ) 540236496Smarius err = at45d_wait_ready(dev, &status); 541236496Smarius if (err != 0) { 542236496Smarius berr = EIO; 543236496Smarius goto out; 544236496Smarius } 545236496Smarius if (bp->bio_cmd == BIO_WRITE) { 546236496Smarius addr = page << sc->pageoffset; 547346557Sian txBuf[0] = PAGE_TO_BUFFER_COMPARE; 548236496Smarius txBuf[1] = ((addr >> 16) & 0xff); 549236496Smarius txBuf[2] = ((addr >> 8) & 0xff); 550236496Smarius txBuf[3] = 0; 551236496Smarius cmd.tx_data_sz = cmd.rx_data_sz = 0; 552236496Smarius err = SPIBUS_TRANSFER(pdev, dev, &cmd); 553236496Smarius if (err == 0) 554236496Smarius err = at45d_wait_ready(dev, &status); 555346557Sian if (err != 0 || (status & STATUS_CMPFAIL)) { 556236496Smarius device_printf(dev, "comparing page " 557346557Sian "%d failed (status=0x%x)\n", page, 558236496Smarius status); 559236496Smarius berr = EIO; 560236496Smarius goto out; 561236496Smarius } 562236496Smarius } 563236496Smarius page++; 564236496Smarius buf += len; 565236496Smarius offset = 0; 566236496Smarius resid -= len; 567236496Smarius if (resid > sc->pagesize) 568236496Smarius len = sc->pagesize; 569236496Smarius else 570236496Smarius len = resid; 571346557Sian if (bp->bio_cmd == BIO_READ) 572346557Sian cmd.rx_data = buf; 573346557Sian else 574346557Sian cmd.tx_data = buf; 575164742Simp } 576236496Smarius out: 577236496Smarius if (berr != 0) { 578236496Smarius bp->bio_flags |= BIO_ERROR; 579236496Smarius bp->bio_error = berr; 580236496Smarius } 581236496Smarius bp->bio_resid = resid; 582164742Simp biodone(bp); 583164742Simp } 584164742Simp} 585164742Simp 586164742Simpstatic devclass_t at45d_devclass; 587164742Simp 588164742Simpstatic device_method_t at45d_methods[] = { 589164742Simp /* Device interface */ 590164742Simp DEVMETHOD(device_probe, at45d_probe), 591164742Simp DEVMETHOD(device_attach, at45d_attach), 592164742Simp DEVMETHOD(device_detach, at45d_detach), 593164742Simp 594236496Smarius DEVMETHOD_END 595164742Simp}; 596164742Simp 597164742Simpstatic driver_t at45d_driver = { 598164742Simp "at45d", 599164742Simp at45d_methods, 600164742Simp sizeof(struct at45d_softc), 601164742Simp}; 602164742Simp 603236496SmariusDRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, NULL, NULL); 604331506SianMODULE_DEPEND(at45d, spibus, 1, 1, 1); 605346557Sian#ifdef FDT 606346557SianMODULE_DEPEND(at45d, fdt_slicer, 1, 1, 1); 607346557Sian#endif 608346557Sian 609