at45d.c revision 164742
1201806Sbz/*- 2201806Sbz * Copyright (c) 2006 M. Warner Losh. All rights reserved. 3201806Sbz * 4201806Sbz * Redistribution and use in source and binary forms, with or without 5201806Sbz * modification, are permitted provided that the following conditions 6201806Sbz * are met: 7201806Sbz * 1. Redistributions of source code must retain the above copyright 8201806Sbz * notice, this list of conditions and the following disclaimer. 9201806Sbz * 2. Redistributions in binary form must reproduce the above copyright 10201806Sbz * notice, this list of conditions and the following disclaimer in the 11201806Sbz * documentation and/or other materials provided with the distribution. 12201806Sbz * 13201806Sbz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14201806Sbz * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15201806Sbz * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16201806Sbz * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17201806Sbz * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18201806Sbz * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19201806Sbz * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20201806Sbz * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21201806Sbz * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22201806Sbz * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23201806Sbz */ 24201806Sbz 25201806Sbz#include <sys/cdefs.h> 26201806Sbz__FBSDID("$FreeBSD: head/sys/dev/flash/at45d.c 164742 2006-11-29 08:05:55Z imp $"); 27201806Sbz 28201806Sbz#include <sys/param.h> 29201806Sbz#include <sys/systm.h> 30201806Sbz#include <sys/bio.h> 31201806Sbz#include <sys/bus.h> 32201806Sbz#include <sys/conf.h> 33201806Sbz#include <sys/gpio.h> 34201806Sbz#include <sys/kernel.h> 35201806Sbz#include <sys/kthread.h> 36201806Sbz#include <sys/lock.h> 37201806Sbz#include <sys/mbuf.h> 38201806Sbz#include <sys/malloc.h> 39201806Sbz#include <sys/module.h> 40201806Sbz#include <sys/mutex.h> 41201806Sbz#include <geom/geom_disk.h> 42201806Sbz 43201897Sbz#include <dev/spibus/spi.h> 44201806Sbz#include "spibus_if.h" 45201806Sbz 46201806Sbzstruct at45d_softc 47201806Sbz{ 48201806Sbz struct intr_config_hook config_intrhook; 49201806Sbz device_t dev; 50201806Sbz struct mtx sc_mtx; 51201806Sbz struct disk *disk; 52201806Sbz struct proc *p; 53201806Sbz struct bio_queue_head bio_queue; 54201806Sbz}; 55201806Sbz 56201806Sbz#define AT45D_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) 57201806Sbz#define AT45D_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) 58201806Sbz#define AT45D_LOCK_INIT(_sc) \ 59201806Sbz mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ 60201806Sbz "at45d", MTX_DEF) 61201806Sbz#define AT45D_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); 62201806Sbz#define AT45D_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED); 63201806Sbz#define AT45D_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED); 64201806Sbz 65201806Sbzstatic void at45d_delayed_attach(void *xsc); 66201806Sbz 67201806Sbz/* disk routines */ 68201806Sbzstatic int at45d_open(struct disk *dp); 69201806Sbzstatic int at45d_close(struct disk *dp); 70201806Sbzstatic void at45d_strategy(struct bio *bp); 71201806Sbzstatic void at45d_task(void *arg); 72201806Sbz 73201806Sbz#define CONTINUOUS_ARRAY_READ 0xE8 74201806Sbz#define CONTINUOUS_ARRAY_READ_HF 0x0B 75201806Sbz#define CONTINUOUS_ARRAY_READ_LF 0x03 76201806Sbz#define STATUS_REGISTER_READ 0xD7 77201806Sbz#define PROGRAM_THROUGH_BUFFER 0x82 78201806Sbz#define MANUFACTURER_ID 0x9F 79201806Sbz 80201806Sbzstatic uint8_t 81201806Sbzat45d_get_status(device_t dev) 82201806Sbz{ 83201806Sbz uint8_t txBuf[8], rxBuf[8]; 84201806Sbz struct spi_command cmd; 85201806Sbz int err; 86201806Sbz 87201806Sbz memset(&cmd, 0, sizeof(cmd)); 88201806Sbz memset(txBuf, 0, sizeof(txBuf)); 89201806Sbz memset(rxBuf, 0, sizeof(rxBuf)); 90201806Sbz 91201806Sbz txBuf[0] = STATUS_REGISTER_READ; 92201806Sbz cmd.tx_cmd = txBuf; 93201806Sbz cmd.rx_cmd = rxBuf; 94201806Sbz cmd.rx_cmd_sz = 2; 95 cmd.tx_cmd_sz = 2; 96 err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd); 97 return (rxBuf[1]); 98} 99 100static void 101at45d_wait_for_device_ready(device_t dev) 102{ 103 while (!(at45d_get_status(dev) & 0x80)) 104 continue; 105} 106 107static int 108at45d_get_mfg_info(device_t dev, uint8_t *resp) 109{ 110 uint8_t txBuf[8], rxBuf[8]; 111 struct spi_command cmd; 112 int err; 113 114 memset(&cmd, 0, sizeof(cmd)); 115 memset(txBuf, 0, sizeof(txBuf)); 116 memset(rxBuf, 0, sizeof(rxBuf)); 117 118 txBuf[0] = MANUFACTURER_ID; 119 cmd.tx_cmd = &txBuf; 120 cmd.rx_cmd = &rxBuf; 121 cmd.tx_cmd_sz = 5; 122 cmd.rx_cmd_sz = 5; 123 err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd); 124 if (err) 125 return (err); 126 memcpy(resp, rxBuf + 1, 4); 127 // XXX We really should 'decode' the reply into some kind of 128 // XXX structure. To be generic (and not just for atmel parts) 129 // XXX we'd have to loop until we got a full reply. 130 return (0); 131} 132 133static int 134at45d_probe(device_t dev) 135{ 136 device_set_desc(dev, "AT45 Flash Family"); 137 return (0); 138} 139 140static int 141at45d_attach(device_t dev) 142{ 143 struct at45d_softc *sc; 144 145 sc = device_get_softc(dev); 146 sc->dev = dev; 147 AT45D_LOCK_INIT(sc); 148 149 /* We'll see what kind of flash we have later... */ 150 sc->config_intrhook.ich_func = at45d_delayed_attach; 151 sc->config_intrhook.ich_arg = sc; 152 if (config_intrhook_establish(&sc->config_intrhook) != 0) 153 device_printf(dev, "config_intrhook_establish failed\n"); 154 return (0); 155} 156 157static int 158at45d_detach(device_t dev) 159{ 160 return EIO; 161} 162 163static void 164at45d_delayed_attach(void *xsc) 165{ 166 struct at45d_softc *sc = xsc; 167 uint8_t buf[4]; 168 169 at45d_get_mfg_info(sc->dev, buf); 170 at45d_wait_for_device_ready(sc->dev); 171 172 sc->disk = disk_alloc(); 173 sc->disk->d_open = at45d_open; 174 sc->disk->d_close = at45d_close; 175 sc->disk->d_strategy = at45d_strategy; 176 sc->disk->d_name = "flash/spi"; 177 sc->disk->d_drv1 = sc; 178 sc->disk->d_maxsize = DFLTPHYS; 179 sc->disk->d_sectorsize = 1056; /* XXX */ 180 sc->disk->d_mediasize = 8192 * 1056; /* XXX */ 181 sc->disk->d_unit = device_get_unit(sc->dev); 182 disk_create(sc->disk, DISK_VERSION); 183 bioq_init(&sc->bio_queue); 184 kthread_create(&at45d_task, sc, &sc->p, 0, 0, "task: at45d flash"); 185 186 config_intrhook_disestablish(&sc->config_intrhook); 187} 188 189static int 190at45d_open(struct disk *dp) 191{ 192 return 0; 193} 194 195static int 196at45d_close(struct disk *dp) 197{ 198 return 0; 199} 200 201static void 202at45d_strategy(struct bio *bp) 203{ 204 struct at45d_softc *sc; 205 206 sc = (struct at45d_softc *)bp->bio_disk->d_drv1; 207 AT45D_LOCK(sc); 208 bioq_disksort(&sc->bio_queue, bp); 209 wakeup(sc); 210 AT45D_UNLOCK(sc); 211} 212 213static void 214at45d_task(void *arg) 215{ 216 struct at45d_softc *sc = (struct at45d_softc*)arg; 217 struct bio *bp; 218 uint8_t txBuf[8], rxBuf[8]; 219 struct spi_command cmd; 220 int sz; 221 daddr_t block, end; 222 device_t dev, pdev; 223 int err; 224 225 for (;;) { 226 dev = sc->dev; 227 pdev = device_get_parent(dev); 228 AT45D_LOCK(sc); 229 do { 230 bp = bioq_first(&sc->bio_queue); 231 if (bp == NULL) 232 msleep(sc, &sc->sc_mtx, PRIBIO, "jobqueue", 0); 233 } while (bp == NULL); 234 bioq_remove(&sc->bio_queue, bp); 235 AT45D_UNLOCK(sc); 236 sz = sc->disk->d_sectorsize; 237 end = bp->bio_pblkno + (bp->bio_bcount / sz); 238 for (block = bp->bio_pblkno; block < end; block++) { 239 char *vaddr = bp->bio_data + (block - bp->bio_pblkno) * sz; 240 if (bp->bio_cmd == BIO_READ) { 241 txBuf[0] = CONTINUOUS_ARRAY_READ_HF; 242 cmd.tx_cmd_sz = 5; 243 cmd.rx_cmd_sz = 5; 244 } else { 245 txBuf[0] = PROGRAM_THROUGH_BUFFER; 246 cmd.tx_cmd_sz = 4; 247 cmd.rx_cmd_sz = 4; 248 } 249 // XXX only works on certain devices... Fixme 250 txBuf[1] = ((block >> 5) & 0xFF); 251 txBuf[2] = ((block << 3) & 0xF8); 252 txBuf[3] = 0; 253 txBuf[4] = 0; 254 cmd.tx_cmd = txBuf; 255 cmd.rx_cmd = rxBuf; 256 cmd.tx_data = vaddr; 257 cmd.tx_data_sz = sz; 258 cmd.rx_data = vaddr; 259 cmd.rx_data_sz = sz; 260 err = SPIBUS_TRANSFER(pdev, dev, &cmd); 261 // XXX err check? 262 } 263 biodone(bp); 264 } 265} 266 267static devclass_t at45d_devclass; 268 269static device_method_t at45d_methods[] = { 270 /* Device interface */ 271 DEVMETHOD(device_probe, at45d_probe), 272 DEVMETHOD(device_attach, at45d_attach), 273 DEVMETHOD(device_detach, at45d_detach), 274 275 { 0, 0 } 276}; 277 278static driver_t at45d_driver = { 279 "at45d", 280 at45d_methods, 281 sizeof(struct at45d_softc), 282}; 283 284DRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, 0, 0); 285