at91_spi.c revision 155324
1/*- 2 * Copyright (c) 2006 M. Warner Losh. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 */ 24 25#include <sys/cdefs.h> 26__FBSDID("$FreeBSD: head/sys/arm/at91/at91_spi.c 155324 2006-02-04 23:32:13Z imp $"); 27 28#include <sys/param.h> 29#include <sys/systm.h> 30#include <sys/bus.h> 31#include <sys/conf.h> 32#include <sys/kernel.h> 33#include <sys/lock.h> 34#include <sys/mbuf.h> 35#include <sys/malloc.h> 36#include <sys/module.h> 37#include <sys/mutex.h> 38#include <sys/rman.h> 39#include <machine/bus.h> 40 41#include <arm/at91/at91_spireg.h> 42#include <arm/at91/at91_spiio.h> 43 44struct at91_spi_softc 45{ 46 device_t dev; /* Myself */ 47 void *intrhand; /* Interrupt handle */ 48 struct resource *irq_res; /* IRQ resource */ 49 struct resource *mem_res; /* Memory resource */ 50 struct mtx sc_mtx; /* basically a perimeter lock */ 51 int flags; 52#define XFER_PENDING 1 /* true when transfer taking place */ 53#define OPENED 2 /* Device opened */ 54#define RXRDY 4 55#define TXCOMP 8 56#define TXRDY 0x10 57 struct cdev *cdev; 58}; 59 60static inline uint32_t 61RD4(struct at91_spi_softc *sc, bus_size_t off) 62{ 63 return bus_read_4(sc->mem_res, off); 64} 65 66static inline void 67WR4(struct at91_spi_softc *sc, bus_size_t off, uint32_t val) 68{ 69 bus_write_4(sc->mem_res, off, val); 70} 71 72#define AT91_SPI_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) 73#define AT91_SPI_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) 74#define AT91_SPI_LOCK_INIT(_sc) \ 75 mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ 76 "spi", MTX_DEF) 77#define AT91_SPI_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); 78#define AT91_SPI_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED); 79#define AT91_SPI_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED); 80#define CDEV2SOFTC(dev) ((dev)->si_drv1) 81 82static devclass_t at91_spi_devclass; 83 84/* bus entry points */ 85 86static int at91_spi_probe(device_t dev); 87static int at91_spi_attach(device_t dev); 88static int at91_spi_detach(device_t dev); 89static void at91_spi_intr(void *); 90 91/* helper routines */ 92static int at91_spi_activate(device_t dev); 93static void at91_spi_deactivate(device_t dev); 94 95/* cdev routines */ 96static d_open_t at91_spi_open; 97static d_close_t at91_spi_close; 98static d_ioctl_t at91_spi_ioctl; 99 100static struct cdevsw at91_spi_cdevsw = 101{ 102 .d_version = D_VERSION, 103 .d_open = at91_spi_open, 104 .d_close = at91_spi_close, 105 .d_ioctl = at91_spi_ioctl 106}; 107 108static int 109at91_spi_probe(device_t dev) 110{ 111 device_set_desc(dev, "SPI"); 112 return (0); 113} 114 115static int 116at91_spi_attach(device_t dev) 117{ 118 struct at91_spi_softc *sc = device_get_softc(dev); 119 int err; 120 121 sc->dev = dev; 122 err = at91_spi_activate(dev); 123 if (err) 124 goto out; 125 126 AT91_SPI_LOCK_INIT(sc); 127 128 /* 129 * Activate the interrupt 130 */ 131 err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE, 132 at91_spi_intr, sc, &sc->intrhand); 133 if (err) { 134 AT91_SPI_LOCK_DESTROY(sc); 135 goto out; 136 } 137 sc->cdev = make_dev(&at91_spi_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, 138 "spi%d", device_get_unit(dev)); 139 if (sc->cdev == NULL) { 140 err = ENOMEM; 141 goto out; 142 } 143 sc->cdev->si_drv1 = sc; 144#if 0 145 /* init */ 146 sc->cwgr = SPI_CWGR_CKDIV(1) | 147 SPI_CWGR_CHDIV(SPI_CWGR_DIV(SPI_DEF_CLK)) | 148 SPI_CWGR_CLDIV(SPI_CWGR_DIV(SPI_DEF_CLK)); 149 150 WR4(sc, SPI_CR, SPI_CR_SWRST); 151 WR4(sc, SPI_CR, SPI_CR_MSEN | SPI_CR_SVDIS); 152 WR4(sc, SPI_CWGR, sc->cwgr); 153#endif 154out:; 155 if (err) 156 at91_spi_deactivate(dev); 157 return (err); 158} 159 160static int 161at91_spi_detach(device_t dev) 162{ 163 return (EBUSY); /* XXX */ 164} 165 166static int 167at91_spi_activate(device_t dev) 168{ 169 struct at91_spi_softc *sc; 170 int rid; 171 172 sc = device_get_softc(dev); 173 rid = 0; 174 sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, 175 RF_ACTIVE); 176 if (sc->mem_res == NULL) 177 goto errout; 178 rid = 0; 179 sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, 180 RF_ACTIVE); 181 if (sc->mem_res == NULL) 182 goto errout; 183 return (0); 184errout: 185 at91_spi_deactivate(dev); 186 return (ENOMEM); 187} 188 189static void 190at91_spi_deactivate(device_t dev) 191{ 192 struct at91_spi_softc *sc; 193 194 sc = device_get_softc(dev); 195 if (sc->intrhand) 196 bus_teardown_intr(dev, sc->irq_res, sc->intrhand); 197 sc->intrhand = 0; 198 bus_generic_detach(sc->dev); 199 if (sc->mem_res) 200 bus_release_resource(dev, SYS_RES_IOPORT, 201 rman_get_rid(sc->mem_res), sc->mem_res); 202 sc->mem_res = 0; 203 if (sc->irq_res) 204 bus_release_resource(dev, SYS_RES_IRQ, 205 rman_get_rid(sc->irq_res), sc->irq_res); 206 sc->irq_res = 0; 207 return; 208} 209 210static void 211at91_spi_intr(void *xsc) 212{ 213 struct at91_spi_softc *sc = xsc; 214#if 0 215 uint32_t status; 216 217 /* Reading the status also clears the interrupt */ 218 status = RD4(sc, SPI_SR); 219 if (status == 0) 220 return; 221 AT91_SPI_LOCK(sc); 222 if (status & SPI_SR_RXRDY) 223 sc->flags |= RXRDY; 224 if (status & SPI_SR_TXCOMP) 225 sc->flags |= TXCOMP; 226 if (status & SPI_SR_TXRDY) 227 sc->flags |= TXRDY; 228 AT91_SPI_UNLOCK(sc); 229#endif 230 wakeup(sc); 231 return; 232} 233 234static int 235at91_spi_open(struct cdev *dev, int oflags, int devtype, struct thread *td) 236{ 237 struct at91_spi_softc *sc; 238 239 sc = CDEV2SOFTC(dev); 240 AT91_SPI_LOCK(sc); 241 if (!(sc->flags & OPENED)) { 242 sc->flags |= OPENED; 243#if 0 244 WR4(sc, SPI_IER, SPI_SR_TXCOMP | SPI_SR_RXRDY | SPI_SR_TXRDY | 245 SPI_SR_OVRE | SPI_SR_UNRE | SPI_SR_NACK); 246#endif 247 } 248 AT91_SPI_UNLOCK(sc); 249 return (0); 250} 251 252static int 253at91_spi_close(struct cdev *dev, int fflag, int devtype, struct thread *td) 254{ 255 struct at91_spi_softc *sc; 256 257 sc = CDEV2SOFTC(dev); 258 AT91_SPI_LOCK(sc); 259 sc->flags &= ~OPENED; 260#if 0 261 WR4(sc, SPI_IDR, SPI_SR_TXCOMP | SPI_SR_RXRDY | SPI_SR_TXRDY | 262 SPI_SR_OVRE | SPI_SR_UNRE | SPI_SR_NACK); 263#endif 264 AT91_SPI_UNLOCK(sc); 265 return (0); 266} 267 268static int 269at91_spi_read_master(struct at91_spi_softc *sc, struct at91_spi_io *xfr) 270{ 271#if 1 272 return ENOTTY; 273#else 274 uint8_t *walker; 275 uint8_t buffer[256]; 276 size_t len; 277 int err = 0; 278 279 if (xfr->xfer_len > sizeof(buffer)) 280 return (EINVAL); 281 walker = buffer; 282 len = xfr->xfer_len; 283 RD4(sc, SPI_RHR); 284 // Master mode, with the right address and interal addr size 285 WR4(sc, SPI_MMR, SPI_MMR_IADRSZ(xfr->iadrsz) | SPI_MMR_MREAD | 286 SPI_MMR_DADR(xfr->dadr)); 287 WR4(sc, SPI_IADR, xfr->iadr); 288 WR4(sc, SPI_CR, SPI_CR_START); 289 while (len-- > 1) { 290 while (!(sc->flags & RXRDY)) { 291 err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH, "spird", 292 0); 293 if (err) 294 return (err); 295 } 296 sc->flags &= ~RXRDY; 297 *walker++ = RD4(sc, SPI_RHR) & 0xff; 298 } 299 WR4(sc, SPI_CR, SPI_CR_STOP); 300 while (!(sc->flags & TXCOMP)) { 301 err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH, "spird2", 0); 302 if (err) 303 return (err); 304 } 305 sc->flags &= ~TXCOMP; 306 *walker = RD4(sc, SPI_RHR) & 0xff; 307 if (xfr->xfer_buf) { 308 AT91_SPI_UNLOCK(sc); 309 err = copyout(buffer, xfr->xfer_buf, xfr->xfer_len); 310 AT91_SPI_LOCK(sc); 311 } 312 return (err); 313#endif 314} 315 316static int 317at91_spi_write_master(struct at91_spi_softc *sc, struct at91_spi_io *xfr) 318{ 319#if 1 320 return ENOTTY; 321#else 322 uint8_t *walker; 323 uint8_t buffer[256]; 324 size_t len; 325 int err; 326 327 if (xfr->xfer_len > sizeof(buffer)) 328 return (EINVAL); 329 walker = buffer; 330 len = xfr->xfer_len; 331 AT91_SPI_UNLOCK(sc); 332 err = copyin(xfr->xfer_buf, buffer, xfr->xfer_len); 333 AT91_SPI_LOCK(sc); 334 if (err) 335 return (err); 336 /* Setup the xfr for later readback */ 337 xfr->xfer_buf = 0; 338 xfr->xfer_len = 1; 339 while (len--) { 340 WR4(sc, SPI_MMR, SPI_MMR_IADRSZ(xfr->iadrsz) | SPI_MMR_MWRITE | 341 SPI_MMR_DADR(xfr->dadr)); 342 WR4(sc, SPI_IADR, xfr->iadr++); 343 WR4(sc, SPI_THR, *walker++); 344 WR4(sc, SPI_CR, SPI_CR_START); 345 /* 346 * If we get signal while waiting for TXRDY, make sure we 347 * try to stop this device 348 */ 349 while (!(sc->flags & TXRDY)) { 350 err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH, "spiwr", 351 0); 352 if (err) 353 break; 354 } 355 WR4(sc, SPI_CR, SPI_CR_STOP); 356 if (err) 357 return (err); 358 while (!(sc->flags & TXCOMP)) { 359 err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH, "spiwr2", 360 0); 361 if (err) 362 return (err); 363 } 364 /* Readback */ 365 at91_spi_read_master(sc, xfr); 366 } 367 return (err); 368#endif 369} 370 371static int 372at91_spi_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, 373 struct thread *td) 374{ 375 int err = 0; 376 struct at91_spi_softc *sc; 377 378 sc = CDEV2SOFTC(dev); 379 AT91_SPI_LOCK(sc); 380 while (sc->flags & XFER_PENDING) { 381 err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH, 382 "spiwait", 0); 383 if (err) { 384 AT91_SPI_UNLOCK(sc); 385 return (err); 386 } 387 } 388 sc->flags |= XFER_PENDING; 389 390 switch (cmd) 391 { 392 case SPIIOCXFER: 393 { 394 struct at91_spi_io *xfr = (struct at91_spi_io *)data; 395 switch (xfr->type) 396 { 397 case SPI_IO_READ_MASTER: 398 err = at91_spi_read_master(sc, xfr); 399 break; 400 case SPI_IO_WRITE_MASTER: 401 err = at91_spi_write_master(sc, xfr); 402 break; 403 default: 404 err = EINVAL; 405 break; 406 } 407 break; 408 } 409 410 case SPIIOCSETCLOCK: 411 { 412#if 0 413 struct at91_spi_clock *spick = (struct at91_spi_clock *)data; 414 415 sc->cwgr = SPI_CWGR_CKDIV(spick->ckdiv) | 416 SPI_CWGR_CHDIV(SPI_CWGR_DIV(spick->high_rate)) | 417 SPI_CWGR_CLDIV(SPI_CWGR_DIV(spick->low_rate)); 418 WR4(sc, SPI_CR, SPI_CR_SWRST); 419 WR4(sc, SPI_CR, SPI_CR_MSEN | SPI_CR_SVDIS); 420 WR4(sc, SPI_CWGR, sc->cwgr); 421#else 422 err = ENOTTY; 423#endif 424 break; 425 } 426 default: 427 err = ENOTTY; 428 break; 429 } 430 sc->flags &= ~XFER_PENDING; 431 AT91_SPI_UNLOCK(sc); 432 wakeup(sc); 433 return err; 434} 435 436static device_method_t at91_spi_methods[] = { 437 /* Device interface */ 438 DEVMETHOD(device_probe, at91_spi_probe), 439 DEVMETHOD(device_attach, at91_spi_attach), 440 DEVMETHOD(device_detach, at91_spi_detach), 441 442 { 0, 0 } 443}; 444 445static driver_t at91_spi_driver = { 446 "at91_spi", 447 at91_spi_methods, 448 sizeof(struct at91_spi_softc), 449}; 450 451DRIVER_MODULE(at91_spi, atmelarm, at91_spi_driver, at91_spi_devclass, 0, 0); 452