/* * Opencore tiny_spi driver * * http://opencores.org/project,tiny_spi * * based on bfin_spi.c * Copyright (c) 2005-2008 Analog Devices Inc. * Copyright (C) 2010 Thomas Chou * * Licensed under the GPL-2 or later. */ #include #include #include #include #include #define TINY_SPI_STATUS_TXE 0x1 #define TINY_SPI_STATUS_TXR 0x2 struct tiny_spi_regs { unsigned rxdata; /* Rx data reg */ unsigned txdata; /* Tx data reg */ unsigned status; /* Status reg */ unsigned control; /* Control reg */ unsigned baud; /* Baud reg */ }; struct tiny_spi_host { uint base; uint freq; uint baudwidth; }; static const struct tiny_spi_host tiny_spi_host_list[] = CONFIG_SYS_TINY_SPI_LIST; struct tiny_spi_slave { struct spi_slave slave; const struct tiny_spi_host *host; uint mode; uint baud; uint flg; }; #define to_tiny_spi_slave(s) container_of(s, struct tiny_spi_slave, slave) int spi_cs_is_valid(unsigned int bus, unsigned int cs) { return bus < ARRAY_SIZE(tiny_spi_host_list) && gpio_is_valid(cs); } void spi_cs_activate(struct spi_slave *slave) { struct tiny_spi_slave *tiny_spi = to_tiny_spi_slave(slave); unsigned int cs = slave->cs; gpio_set_value(cs, tiny_spi->flg); debug("%s: SPI_CS_GPIO:%x\n", __func__, gpio_get_value(cs)); } void spi_cs_deactivate(struct spi_slave *slave) { struct tiny_spi_slave *tiny_spi = to_tiny_spi_slave(slave); unsigned int cs = slave->cs; gpio_set_value(cs, !tiny_spi->flg); debug("%s: SPI_CS_GPIO:%x\n", __func__, gpio_get_value(cs)); } void spi_set_speed(struct spi_slave *slave, uint hz) { struct tiny_spi_slave *tiny_spi = to_tiny_spi_slave(slave); const struct tiny_spi_host *host = tiny_spi->host; tiny_spi->baud = min(DIV_ROUND_UP(host->freq, hz * 2), (1 << host->baudwidth)) - 1; debug("%s: speed %u actual %u\n", __func__, hz, host->freq / ((tiny_spi->baud + 1) * 2)); } void spi_init(void) { } struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, unsigned int hz, unsigned int mode) { struct tiny_spi_slave *tiny_spi; if (!spi_cs_is_valid(bus, cs) || gpio_request(cs, "tiny_spi")) return NULL; tiny_spi = spi_alloc_slave(struct tiny_spi_slave, bus, cs); if (!tiny_spi) return NULL; tiny_spi->host = &tiny_spi_host_list[bus]; tiny_spi->mode = mode & (SPI_CPOL | SPI_CPHA); tiny_spi->flg = mode & SPI_CS_HIGH ? 1 : 0; spi_set_speed(&tiny_spi->slave, hz); debug("%s: bus:%i cs:%i base:%lx\n", __func__, bus, cs, tiny_spi->host->base); return &tiny_spi->slave; } void spi_free_slave(struct spi_slave *slave) { struct tiny_spi_slave *tiny_spi = to_tiny_spi_slave(slave); gpio_free(slave->cs); free(tiny_spi); } int spi_claim_bus(struct spi_slave *slave) { struct tiny_spi_slave *tiny_spi = to_tiny_spi_slave(slave); struct tiny_spi_regs *regs = (void *)tiny_spi->host->base; debug("%s: bus:%i cs:%i\n", __func__, slave->bus, slave->cs); gpio_direction_output(slave->cs, !tiny_spi->flg); writel(tiny_spi->mode, ®s->control); writel(tiny_spi->baud, ®s->baud); return 0; } void spi_release_bus(struct spi_slave *slave) { debug("%s: bus:%i cs:%i\n", __func__, slave->bus, slave->cs); } #ifndef CONFIG_TINY_SPI_IDLE_VAL # define CONFIG_TINY_SPI_IDLE_VAL 0xff #endif int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout, void *din, unsigned long flags) { struct tiny_spi_slave *tiny_spi = to_tiny_spi_slave(slave); struct tiny_spi_regs *regs = (void *)tiny_spi->host->base; const u8 *txp = dout; u8 *rxp = din; uint bytes = bitlen / 8; uint i; debug("%s: bus:%i cs:%i bitlen:%i bytes:%i flags:%lx\n", __func__, slave->bus, slave->cs, bitlen, bytes, flags); if (bitlen == 0) goto done; /* assume to do 8 bits transfers */ if (bitlen % 8) { flags |= SPI_XFER_END; goto done; } if (flags & SPI_XFER_BEGIN) spi_cs_activate(slave); /* we need to tighten the transfer loop */ if (txp && rxp) { writeb(*txp++, ®s->txdata); if (bytes > 1) { writeb(*txp++, ®s->txdata); for (i = 2; i < bytes; i++) { u8 rx, tx = *txp++; while (!(readb(®s->status) & TINY_SPI_STATUS_TXR)) ; rx = readb(®s->txdata); writeb(tx, ®s->txdata); *rxp++ = rx; } while (!(readb(®s->status) & TINY_SPI_STATUS_TXR)) ; *rxp++ = readb(®s->txdata); } while (!(readb(®s->status) & TINY_SPI_STATUS_TXE)) ; *rxp++ = readb(®s->rxdata); } else if (rxp) { writeb(CONFIG_TINY_SPI_IDLE_VAL, ®s->txdata); if (bytes > 1) { writeb(CONFIG_TINY_SPI_IDLE_VAL, ®s->txdata); for (i = 2; i < bytes; i++) { u8 rx; while (!(readb(®s->status) & TINY_SPI_STATUS_TXR)) ; rx = readb(®s->txdata); writeb(CONFIG_TINY_SPI_IDLE_VAL, ®s->txdata); *rxp++ = rx; } while (!(readb(®s->status) & TINY_SPI_STATUS_TXR)) ; *rxp++ = readb(®s->txdata); } while (!(readb(®s->status) & TINY_SPI_STATUS_TXE)) ; *rxp++ = readb(®s->rxdata); } else if (txp) { writeb(*txp++, ®s->txdata); if (bytes > 1) { writeb(*txp++, ®s->txdata); for (i = 2; i < bytes; i++) { u8 tx = *txp++; while (!(readb(®s->status) & TINY_SPI_STATUS_TXR)) ; writeb(tx, ®s->txdata); } } while (!(readb(®s->status) & TINY_SPI_STATUS_TXE)) ; } else { writeb(CONFIG_TINY_SPI_IDLE_VAL, ®s->txdata); if (bytes > 1) { writeb(CONFIG_TINY_SPI_IDLE_VAL, ®s->txdata); for (i = 2; i < bytes; i++) { while (!(readb(®s->status) & TINY_SPI_STATUS_TXR)) ; writeb(CONFIG_TINY_SPI_IDLE_VAL, ®s->txdata); } } while (!(readb(®s->status) & TINY_SPI_STATUS_TXE)) ; } done: if (flags & SPI_XFER_END) spi_cs_deactivate(slave); return 0; }