blob: 4de5d008aaf9f4e177b31c91709c7d77303c318e [file] [log] [blame]
/*
* 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, &regs->control);
writel(tiny_spi->baud, &regs->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++, &regs->txdata);
if (bytes > 1) {
writeb(*txp++, &regs->txdata);
for (i = 2; i < bytes; i++) {
u8 rx, tx = *txp++;
while (!(readb(&regs->status) &
TINY_SPI_STATUS_TXR))
;
rx = readb(&regs->txdata);
writeb(tx, &regs->txdata);
*rxp++ = rx;
}
while (!(readb(&regs->status) &
TINY_SPI_STATUS_TXR))
;
*rxp++ = readb(&regs->txdata);
}
while (!(readb(&regs->status) &
TINY_SPI_STATUS_TXE))
;
*rxp++ = readb(&regs->rxdata);
} else if (rxp) {
writeb(CONFIG_TINY_SPI_IDLE_VAL, &regs->txdata);
if (bytes > 1) {
writeb(CONFIG_TINY_SPI_IDLE_VAL,
&regs->txdata);
for (i = 2; i < bytes; i++) {
u8 rx;
while (!(readb(&regs->status) &
TINY_SPI_STATUS_TXR))
;
rx = readb(&regs->txdata);
writeb(CONFIG_TINY_SPI_IDLE_VAL,
&regs->txdata);
*rxp++ = rx;
}
while (!(readb(&regs->status) &
TINY_SPI_STATUS_TXR))
;
*rxp++ = readb(&regs->txdata);
}
while (!(readb(&regs->status) &
TINY_SPI_STATUS_TXE))
;
*rxp++ = readb(&regs->rxdata);
} else if (txp) {
writeb(*txp++, &regs->txdata);
if (bytes > 1) {
writeb(*txp++, &regs->txdata);
for (i = 2; i < bytes; i++) {
u8 tx = *txp++;
while (!(readb(&regs->status) &
TINY_SPI_STATUS_TXR))
;
writeb(tx, &regs->txdata);
}
}
while (!(readb(&regs->status) &
TINY_SPI_STATUS_TXE))
;
} else {
writeb(CONFIG_TINY_SPI_IDLE_VAL, &regs->txdata);
if (bytes > 1) {
writeb(CONFIG_TINY_SPI_IDLE_VAL,
&regs->txdata);
for (i = 2; i < bytes; i++) {
while (!(readb(&regs->status) &
TINY_SPI_STATUS_TXR))
;
writeb(CONFIG_TINY_SPI_IDLE_VAL,
&regs->txdata);
}
}
while (!(readb(&regs->status) &
TINY_SPI_STATUS_TXE))
;
}
done:
if (flags & SPI_XFER_END)
spi_cs_deactivate(slave);
return 0;
}