| /* |
| * 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 <thomas@wytron.com.tw> |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| */ |
| |
| #include <common.h> |
| #include <asm/io.h> |
| #include <malloc.h> |
| #include <spi.h> |
| #include <asm/gpio.h> |
| |
| #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; |
| } |