blob: dd214723842349337bf6ad5a35522bffb2a91376 [file] [log] [blame]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <stdio.h>
#include <stdbool.h>
#include <platsupport/spi.h>
#include <utils/zf_log.h>
#define SPI_PAGE_PADDR 0x7000D000
#define SPI0_PADDR 0x7000D400
#define SPI1_PADDR 0x7000D600
#define SPI2_PADDR 0x7000D800
#define SPI3_PADDR 0x7000DA00
#define SPI4_PADDR 0x7000DC00
#define SPI5_PADDR 0x7000DE00
#define SPI0_OFFSET (SPI0_PADDR - SPI_PAGE_PADDR)
/* COMMAND1 */
#define SPI_CMD1_GO (BIT(31))
#define SPI_CMD1_M_S (BIT(30))
#define SPI_CMD1_MODE_MASK 0x3
#define SPI_CMD1_MODE_SHIFT 28
#define SPI_CMD1_CS_SEL_MASK 0x3
#define SPI_CMD1_CS_SEL_SHIFT 26
#define SPI_CMD1_CS_POL_INACTIVE3 (BIT(25))
#define SPI_CMD1_CS_POL_INACTIVE2 (BIT(24))
#define SPI_CMD1_CS_POL_INACTIVE1 (BIT(23))
#define SPI_CMD1_CS_POL_INACTIVE0 (BIT(22))
#define SPI_CMD1_CS_SW_HW (BIT(21))
#define SPI_CMD1_CS_SW_VAL (BIT(20))
#define SPI_CMD1_IDLE_SDA_MASK 0x3
#define SPI_CMD1_IDLE_SDA_SHIFT 18
#define SPI_CMD1_BIDIR (BIT(17))
#define SPI_CMD1_LSBI_FE (BIT(16))
#define SPI_CMD1_LSBY_FE (BIT(15))
#define SPI_CMD1_BOTH_EN_BIT (BIT(14))
#define SPI_CMD1_BOTH_EN_BYTE (BIT(13))
#define SPI_CMD1_RX_EN (BIT(12))
#define SPI_CMD1_TX_EN (BIT(11))
#define SPI_CMD1_PACKED (BIT(5))
#define SPI_CMD1_BIT_LEN_MASK 0x1F
#define SPI_CMD1_BIT_LEN_SHIFT 0
/* COMMAND2 */
#define SPI_CMD2_TX_CLK_TAP_DELAY (BIT(6))
#define SPI_CMD2_TX_CLK_TAP_DELAY_MASK (0x3F << 6)
#define SPI_CMD2_RX_CLK_TAP_DELAY (BIT(0))
#define SPI_CMD2_RX_CLK_TAP_DELAY_MASK (0x3F << 0)
/* TRANSFER STATUS */
#define SPI_XFER_STS_RDY (BIT(30))
/* FIFO STATUS */
#define SPI_FIFO_STS_CS_INACTIVE (BIT(31))
#define SPI_FIFO_STS_FRAME_END (BIT(30))
#define SPI_FIFO_STS_RX_FIFO_FLUSH (BIT(15))
#define SPI_FIFO_STS_TX_FIFO_FLUSH (BIT(14))
#define SPI_FIFO_STS_ERR (BIT(8))
#define SPI_FIFO_STS_TX_FIFO_OVF (BIT(7))
#define SPI_FIFO_STS_TX_FIFO_UNR (BIT(6))
#define SPI_FIFO_STS_RX_FIFO_OVF (BIT(5))
#define SPI_FIFO_STS_RX_FIFO_UNR (BIT(4))
#define SPI_FIFO_STS_TX_FIFO_FULL (BIT(3))
#define SPI_FIFO_STS_TX_FIFO_EMPTY (BIT(2))
#define SPI_FIFO_STS_RX_FIFO_FULL (BIT(1))
#define SPI_FIFO_STS_RX_FIFO_EMPTY (BIT(0))
/* SPI CS Timing */
#define CS_SETUP_TIME_0_SHIFT 4
#define CS_HOLD_TIME_0_SHIFT 0
/* DMA CTL */
#define SPI_IE_RX (BIT(29))
#define SPI_IE_TX (BIT(28))
#define CLK_TAP_DELAY 0x1f
#define CS_SETUP_TIME 0xf
#define CS_HOLD_TIME 0xf
#define FIFO_SIZE 64
struct spi_regs {
volatile uint32_t command1; /* 000:SPI_COMMAND1 register */
volatile uint32_t command2; /* 004:SPI_COMMAND2 register */
volatile uint32_t timing1; /* 008:SPI_CS_TIM1 register */
volatile uint32_t timing2; /* 00c:SPI_CS_TIM2 register */
volatile uint32_t xfer_status; /* 010:SPI_TRANS_STATUS register */
volatile uint32_t fifo_status; /* 014:SPI_FIFO_STATUS register */
volatile uint32_t tx_data; /* 018:SPI_TX_DATA register */
volatile uint32_t rx_data; /* 01c:SPI_RX_DATA register */
volatile uint32_t dma_ctl; /* 020:SPI_DMA_CTL register */
volatile uint32_t dma_blk; /* 024:SPI_DMA_BLK register */
PAD_STRUCT_BETWEEN(0x24, 0x108, uint32_t);
volatile uint32_t tx_fifo; /* 108:SPI_FIFO1 register */
PAD_STRUCT_BETWEEN(0x108, 0x188, uint32_t);
volatile uint32_t rx_fifo; /* 188:SPI_FIFO2 register */
volatile uint32_t spare_ctl; /* 18c:SPI_SPARE_CTRL register */
};
struct spi_bus {
volatile struct spi_regs* regs;
uint32_t clock_mode; // see table 147 on page 2436 of the TRM
bool in_progress;
uint8_t *txbuf;
uint8_t *rxbuf;
size_t txsize, rxsize;
spi_chipselect_fn cs;
spi_callback_fn cb;
void* token;
spi_slave_config_t *curr_slave;
};
uint32_t spi_controller_offsets[] = {
[SPI0] = SPI0_OFFSET
};
static spi_bus_t _spi[] = {
[SPI0] = { .regs = NULL, .clock_mode = 0 },
};
int
tegra_spi_init(enum spi_id id, volatile void* base, spi_chipselect_fn cs_func,
mux_sys_t* mux_sys, clock_sys_t* clock_sys,
spi_bus_t** ret_spi_bus)
{
spi_bus_t* spi_bus = &_spi[id];
spi_bus->cs = cs_func;
spi_bus->regs = base + spi_controller_offsets[id];
// Clear relevant bits in fifo status register
uint32_t fifo_status = SPI_FIFO_STS_RX_FIFO_FLUSH |
SPI_FIFO_STS_TX_FIFO_FLUSH |
SPI_FIFO_STS_ERR |
SPI_FIFO_STS_TX_FIFO_OVF |
SPI_FIFO_STS_TX_FIFO_UNR |
SPI_FIFO_STS_RX_FIFO_OVF |
SPI_FIFO_STS_RX_FIFO_UNR;
spi_bus->regs->fifo_status = fifo_status;
uint32_t command1 = spi_bus->regs->command1;
command1 |= (spi_bus->clock_mode << SPI_CMD1_MODE_SHIFT);
if (spi_bus->cs != NULL) {
// Use software chip select if spi_chipselect_fn is provided
command1 |= SPI_CMD1_CS_SW_HW;
}
command1 |= SPI_CMD1_TX_EN | SPI_CMD1_RX_EN;
command1 |= (8 - 1) << SPI_CMD1_BIT_LEN_SHIFT;
spi_bus->regs->command1 = command1;
uint32_t command2 = spi_bus->regs->command2;
command2 |= (CLK_TAP_DELAY & SPI_CMD2_RX_CLK_TAP_DELAY_MASK);
spi_bus->regs->command2 = command2;
uint32_t timing1 = spi_bus->regs->timing1;
timing1 |= (CS_SETUP_TIME << CS_SETUP_TIME_0_SHIFT);
timing1 |= (CS_HOLD_TIME << CS_HOLD_TIME_0_SHIFT);
spi_bus->regs->timing1 = timing1;
uint32_t dma_ctl = spi_bus->regs->dma_ctl;
dma_ctl |= SPI_IE_RX | SPI_IE_TX;
spi_bus->regs->dma_ctl = dma_ctl;
spi_bus->regs->xfer_status |= SPI_XFER_STS_RDY;
*ret_spi_bus = spi_bus;
return 0;
}
static void
finish_spi_transfer(spi_bus_t* spi_bus) {
// Drain RX FIFO
size_t size = spi_bus->txsize + spi_bus->rxsize;
for (int i = 0; i < size; i++) {
uint32_t data_in = spi_bus->regs->rx_fifo;
if (spi_bus->rxbuf != NULL) {
spi_bus->rxbuf[i] = (uint8_t) (data_in & 0xFF);
}
}
spi_bus->regs->xfer_status |= SPI_XFER_STS_RDY;
spi_bus->in_progress = false;
// Release chip select
if (spi_bus->cs != NULL) {
spi_bus->cs(spi_bus->curr_slave, SPI_CS_RELAX);
}
spi_bus->cb(spi_bus, size, spi_bus->token);
}
void
spi_handle_irq(spi_bus_t* spi_bus)
{
if (spi_bus->regs->xfer_status & SPI_XFER_STS_RDY) {
finish_spi_transfer(spi_bus);
} else {
uint32_t fifo_status = spi_bus->regs->fifo_status;
ZF_LOGE("FIFO error, status = 0x%08x", fifo_status);
// Abort transfer
spi_bus->regs->command1 &= ~SPI_CMD1_GO;
// Clear FIFO status, flush FIFOs
fifo_status |= SPI_FIFO_STS_RX_FIFO_FLUSH | SPI_FIFO_STS_TX_FIFO_FLUSH;
spi_bus->regs->fifo_status = fifo_status;
// Re-initialize transfer status
spi_bus->regs->xfer_status |= SPI_XFER_STS_RDY;
spi_bus->in_progress = false;
// Release chip select
if (spi_bus->cs != NULL) {
spi_bus->cs(spi_bus->curr_slave, SPI_CS_RELAX);
}
// Indicate failure to user
spi_bus->cb(spi_bus, -1, spi_bus->token);
}
}
static void
start_spi_transfer(spi_bus_t* spi_bus) {
// Assert chip select
if (spi_bus->cs != NULL) {
spi_bus->cs(spi_bus->curr_slave, SPI_CS_ASSERT);
}
size_t size = spi_bus->txsize + spi_bus->rxsize;
spi_bus->regs->dma_blk = size - 1;
// Load TX FIFO
for (int i = 0; i < size; i++) {
if (i < spi_bus->txsize) {
spi_bus->regs->tx_fifo = spi_bus->txbuf[i];
} else {
spi_bus->regs->tx_fifo = 0;
}
}
// Signal transfer to begin
spi_bus->regs->command1 |= SPI_CMD1_GO;
}
int
spi_xfer(spi_bus_t* spi_bus, const void* txdata, size_t txcnt,
void* rxdata, size_t rxcnt, spi_callback_fn cb, void* token)
{
if (spi_bus->in_progress) {
ZF_LOGE("SPI transaction in progress");
return -1;
}
if (txcnt + rxcnt > FIFO_SIZE) {
ZF_LOGE("SPI transaction size (%d) exceeds FIFO size (%d)", txcnt + rxcnt, FIFO_SIZE);
return -2;
}
if (cb == NULL) {
ZF_LOGE("Synchronous SPI transactions are not implemented");
return -3;
}
spi_bus->txbuf = (uint8_t*) txdata;
spi_bus->rxbuf = (uint8_t*) rxdata;
spi_bus->txsize = txcnt;
spi_bus->rxsize = rxcnt;
spi_bus->in_progress = true;
spi_bus->cb = cb;
spi_bus->token = token;
start_spi_transfer(spi_bus);
return 0;
}
void
spi_prepare_transfer(spi_bus_t* spi_bus, const spi_slave_config_t* cfg)
{
// TODO: implement support for multiple slaves
spi_bus->curr_slave = (spi_slave_config_t *)cfg;
}