722 lines
19 KiB
C
722 lines
19 KiB
C
/*
|
|
* Copyright (C) 2007 Apple Inc. All rights reserved.
|
|
* Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
|
|
*
|
|
* This document is the property of Apple Inc.
|
|
* It is considered confidential and proprietary.
|
|
*
|
|
* This document may not be reproduced or transmitted in any form,
|
|
* in whole or in part, without the express written permission of
|
|
* Apple Inc.
|
|
*/
|
|
#include <arch.h>
|
|
#include <debug.h>
|
|
#include <lib/libc.h>
|
|
#include <platform.h>
|
|
#include <platform/int.h>
|
|
#include <platform/clocks.h>
|
|
#include <platform/gpio.h>
|
|
#include <platform/soc/hwclocks.h>
|
|
#include <platform/soc/hwisr.h>
|
|
#include <sys.h>
|
|
#include <sys/task.h>
|
|
|
|
#include "spi.h"
|
|
|
|
#ifndef SPIS_MASK
|
|
#define SPIS_MASK ((1 << SPIS_COUNT) - 1)
|
|
#endif
|
|
|
|
#define SPI_DMA 0
|
|
|
|
#define SPI_AGD_MODE 1
|
|
|
|
//#define DEBUG_SPI_SPEW DEBUG_SPEW
|
|
#ifndef DEBUG_SPI_SPEW
|
|
#define DEBUG_SPI_SPEW (0x7FFFFFFF)
|
|
#endif
|
|
|
|
/* previously from libc.h, but it pollutes the namespace there */
|
|
#define BITS_SHIFT(x, high, low) (((x) >> (low)) & ((1<<((high)-(low)+1))-1))
|
|
|
|
/* spi driver, copy and customize */
|
|
|
|
enum spi_clk {
|
|
PCLK,
|
|
NCLK
|
|
};
|
|
|
|
static struct spi_status_t {
|
|
int bits; // 8, 16, 32
|
|
int clkpol; // clock polarity
|
|
int clkpha; // clock phase
|
|
enum spi_clk clk; // PCLK or NCLK
|
|
int baud;
|
|
bool master;
|
|
bool dma;
|
|
|
|
/* interrupt driven stuff */
|
|
const void *tx_buf;
|
|
size_t tx_pos;
|
|
size_t tx_len;
|
|
void *rx_buf;
|
|
size_t rx_pos;
|
|
size_t rx_len;
|
|
int overrun_errors;
|
|
|
|
struct task_event event;
|
|
|
|
uint32_t shadow_spcon;
|
|
|
|
#if SPI_DMA
|
|
/* dma stuff */
|
|
dma_t tx_dma;
|
|
struct iovec tx_iov;
|
|
struct dma_xfer_t tx_xfer;
|
|
|
|
dma_t rx_dma;
|
|
struct iovec rx_iov;
|
|
struct dma_xfer_t rx_xfer;
|
|
#endif
|
|
|
|
/* completion flags */
|
|
volatile int tx_complete;
|
|
volatile int rx_complete;
|
|
} spi_status[SPIS_COUNT];
|
|
|
|
typedef struct {
|
|
volatile uint32_t *spclkcon;
|
|
volatile uint32_t *spcon;
|
|
volatile uint32_t *spsta;
|
|
volatile uint32_t *sppin;
|
|
volatile uint32_t *sptdat;
|
|
volatile uint32_t *sprdat;
|
|
volatile uint32_t *sppre;
|
|
volatile uint32_t *spcnt;
|
|
volatile uint32_t *spidd;
|
|
volatile uint32_t *spirto;
|
|
volatile uint32_t *spihangd;
|
|
volatile uint32_t *spiswrst;
|
|
volatile uint32_t *spiver;
|
|
volatile uint32_t *sptdcnt;
|
|
uint32_t clock;
|
|
uint32_t irq;
|
|
} spi_regs_t;
|
|
|
|
static const spi_regs_t spi_regs[] = {
|
|
#if SPIS_COUNT > 0
|
|
{ &rSPCLKCON0, &rSPCON0, &rSPSTA0, &rSPPIN0, &rSPTDAT0, &rSPRDAT0, &rSPPRE0, &rSPCNT0,
|
|
&rSPIDD0, &rSPIRTO0, &rSPIHANGD0, &rSPISWRST0, &rSPIVER0, &rSPTDCNT0, CLK_SPI0, INT_SPI0 },
|
|
#endif
|
|
#if SPIS_COUNT > 1
|
|
{ &rSPCLKCON1, &rSPCON1, &rSPSTA1, &rSPPIN1, &rSPTDAT1, &rSPRDAT1, &rSPPRE1, &rSPCNT1,
|
|
&rSPIDD1, &rSPIRTO1, &rSPIHANGD1, &rSPISWRST1, &rSPIVER1, &rSPTDCNT1, CLK_SPI1, INT_SPI1 },
|
|
#endif
|
|
#if SPIS_COUNT > 2
|
|
{ &rSPCLKCON2, &rSPCON2, &rSPSTA2, &rSPPIN2, &rSPTDAT2, &rSPRDAT2, &rSPPRE2, &rSPCNT2,
|
|
&rSPIDD2, &rSPIRTO2, &rSPIHANGD2, &rSPISWRST2, &rSPIVER2, &rSPTDCNT2, CLK_SPI2, INT_SPI2 },
|
|
#endif
|
|
#if SPIS_COUNT > 3
|
|
{ &rSPCLKCON3, &rSPCON3, &rSPSTA3, &rSPPIN3, &rSPTDAT3, &rSPRDAT3, &rSPPRE3, &rSPCNT3,
|
|
&rSPIDD3, &rSPIRTO3, &rSPIHANGD3, &rSPISWRST3, &rSPIVER3, &rSPTDCNT3, CLK_SPI3, INT_SPI3 },
|
|
#endif
|
|
#if SPIS_COUNT > 4
|
|
{ &rSPCLKCON4, &rSPCON4, &rSPSTA4, &rSPPIN4, &rSPTDAT4, &rSPRDAT4, &rSPPRE4, &rSPCNT4,
|
|
&rSPIDD4, &rSPIRTO4, &rSPIHANGD4, &rSPISWRST4, &rSPIVER4, &rSPTDCNT4, CLK_SPI4, INT_SPI4 },
|
|
#endif
|
|
};
|
|
|
|
static const spi_regs_t *spiregs(int port)
|
|
{
|
|
if (port < 0 || port >= SPIS_COUNT || !(SPIS_MASK & (1 << port)))
|
|
panic("Trying to access a nonexisted spi port\n");
|
|
|
|
return &spi_regs[port];
|
|
}
|
|
|
|
static void spi_set_port_enable(int port, bool enable)
|
|
{
|
|
const spi_regs_t *regs = spiregs(port);
|
|
|
|
if (enable)
|
|
*regs->spclkcon = 1;
|
|
else
|
|
*regs->spclkcon = 0;
|
|
}
|
|
|
|
#if SPI_DMA
|
|
static void spi_set_port_dma(int port, bool dma)
|
|
{
|
|
if (dma == spi_status[port].dma)
|
|
return;
|
|
|
|
if (spi_status[port].dma) {
|
|
release_dma_channel(spi_status[port].tx_dma);
|
|
release_dma_channel(spi_status[port].rx_dma);
|
|
spi_status[port].tx_dma = spi_status[port].rx_dma = -1;
|
|
}
|
|
|
|
if (dma) {
|
|
spi_status[port].tx_dma = acquire_dma_channel(DMA_DEVICE_MEMORY, DMA_DEVICE_SPI_0 + port, 1, 1);
|
|
spi_status[port].rx_dma = acquire_dma_channel(DMA_DEVICE_SPI_0 + port, DMA_DEVICE_MEMORY, 1, 1);
|
|
dprintf(DEBUG_INFO, "spi_set_port_dma: allocated channels, tx %d rx %d\n", spi_status[port].tx_dma, spi_status[port].rx_dma);
|
|
}
|
|
|
|
spi_status[port].dma = dma;
|
|
}
|
|
#endif
|
|
|
|
static void spi_set_baud(int port, int baud)
|
|
{
|
|
const spi_regs_t *regs = spiregs(port);
|
|
uint32_t clkrate;
|
|
int div;
|
|
int actual_baud;
|
|
|
|
if (spi_status[port].clk == PCLK)
|
|
clkrate = clock_get_frequency(CLK_PCLK);
|
|
else
|
|
clkrate = clock_get_frequency(CLK_NCLK);
|
|
|
|
div = clkrate / baud;
|
|
#if SPI_VERSION > 0
|
|
if (div < 1) div = 1;
|
|
#else
|
|
if (div < 2) div = 2;
|
|
#endif
|
|
actual_baud = clkrate / div;
|
|
|
|
dprintf(DEBUG_SPI_SPEW, "spi_set_baud: port %d clk %s (%d Hz) baud %d, divider %d, actual baud %d\n",
|
|
port, (spi_status[port].clk == PCLK) ? "PCLK" : "NCLK", clkrate, baud, div, actual_baud);
|
|
|
|
if (div >= (1<<10))
|
|
dprintf(DEBUG_CRITICAL, "spi_set_baud: warning, div 0x%x is too big to fit in register\n", div);
|
|
|
|
*regs->sppre = div;
|
|
spi_status[port].baud = baud;
|
|
}
|
|
|
|
//small routine to get the baudrate for a divider
|
|
//helper function to be able todo automated testing
|
|
static int spi_baud_from_div(int port, int div)
|
|
{
|
|
int clkrate;
|
|
|
|
if (spi_status[port].clk == PCLK)
|
|
clkrate = clock_get_frequency(CLK_PCLK);
|
|
else
|
|
clkrate = clock_get_frequency(CLK_NCLK);
|
|
|
|
if (div < 2) div = 2;
|
|
return (clkrate / div);
|
|
}
|
|
|
|
void spi_set_interdatadelay(int port,int usecs)
|
|
{
|
|
const spi_regs_t *regs = spiregs(port);
|
|
uint32_t clocks;
|
|
|
|
clocks = ((1ULL * clock_get_frequency(CLK_PCLK) * usecs) + 1000000 - 1) / 1000000;
|
|
|
|
*regs->spidd = clocks;
|
|
dprintf(DEBUG_SPI_SPEW, "spidd: %d\n",*regs->spidd);
|
|
}
|
|
|
|
void spi_set_clk(int port, enum spi_clk clk)
|
|
{
|
|
spi_status[port].clk = clk;
|
|
}
|
|
|
|
void spi_setup(int port, int baud, int width, bool master, int clkpol, int clkpha)
|
|
{
|
|
const spi_regs_t *regs = spiregs(port);
|
|
|
|
if (regs == 0)
|
|
return;
|
|
|
|
spi_set_port_enable(port, false);
|
|
|
|
switch (width) {
|
|
default:
|
|
case 8:
|
|
spi_status[port].bits = 0;
|
|
break;
|
|
case 16:
|
|
spi_status[port].bits = 1;
|
|
break;
|
|
case 32:
|
|
spi_status[port].bits = 2;
|
|
break;
|
|
}
|
|
|
|
spi_status[port].clkpol = clkpol ? 1 : 0;
|
|
spi_status[port].clkpha = clkpha ? 1 : 0;
|
|
spi_set_baud(port, baud);
|
|
spi_status[port].master = master;
|
|
|
|
/* set up the control register */
|
|
spi_status[port].shadow_spcon =
|
|
(spi_status[port].clkpha << SPICON_CPHA_SHIFT) |
|
|
(spi_status[port].clkpol << SPICON_CPOL_SHIFT) |
|
|
((spi_status[port].master ? 3 : 0) << SPICON_MASTER_SHIFT) | // if in master mode, enable clock
|
|
((spi_status[port].dma ? 2 : 1) << SPICON_MODE_SHIFT) |
|
|
(0 << SPICON_MSBFT_SHIFT) | // MSB first
|
|
(((spi_status[port].clk == PCLK) ? 0 : 1) << SPICON_CLK_SEL_SHIFT) |
|
|
(spi_status[port].bits << SPICON_BIT_LEN_SHIFT) | // 8/16/32 bit width
|
|
(0 << SPICON_DMA_SIZE_SHIFT); // 4 byte IRQ/DMA
|
|
*regs->spcon = spi_status[port].shadow_spcon;
|
|
|
|
*regs->sppin = (1 << 1);
|
|
|
|
spi_set_port_enable(port, true); // the port has to be enabled before you write to the FIFO.
|
|
|
|
dprintf(DEBUG_SPI_SPEW, "spcon 0x%x\n", *regs->spcon);
|
|
}
|
|
|
|
static void spi_flush_tx_fifo(int port)
|
|
{
|
|
const spi_regs_t *regs = spiregs(port);
|
|
|
|
*regs->spclkcon |= (1<<2);
|
|
}
|
|
|
|
static void spi_flush_rx_fifo(int port)
|
|
{
|
|
const spi_regs_t *regs = spiregs(port);
|
|
|
|
*regs->spclkcon |= (1<<3);
|
|
}
|
|
|
|
static void spi_interrupt(void *arg)
|
|
{
|
|
int port = (int)arg;
|
|
const spi_regs_t *regs = spiregs(port);
|
|
uint32_t status;
|
|
uint32_t i;
|
|
|
|
status = *regs->spsta;
|
|
*regs->spsta = status;
|
|
|
|
dprintf(DEBUG_SPI_SPEW, "spi_interrupt(): %d, status 0x%08x / 0x%08x\n", port, status, *regs->spcon);
|
|
|
|
if (status & (1 << 3)) { // data collision error
|
|
dprintf(DEBUG_SPI_SPEW, "spi_int, port %d: data collision (lost rx bytes)\n", port);
|
|
spi_status[port].overrun_errors++;
|
|
}
|
|
if (status & (1 << 0)) { // rx done
|
|
dprintf(DEBUG_SPI_SPEW, "spi_int, port %d: rx done, status 0x%x, tx pos %zu, rx pos %zu\n",
|
|
port, status, spi_status[port].tx_pos, spi_status[port].rx_pos);
|
|
dprintf(DEBUG_SPI_SPEW, "\ttx fifo %d rx fifo %d\n",
|
|
BITS_SHIFT(status, SPSTA_TX_FIFO_BIT_HI, SPSTA_TX_FIFO_BIT_LO),
|
|
BITS_SHIFT(status, SPSTA_RX_FIFO_BIT_HI, SPSTA_RX_FIFO_BIT_LO));
|
|
if (!(status & ((1 << 22) | (1 << 1))))
|
|
goto handle_rx;
|
|
}
|
|
if (status & ((1 << 22) | (1 << 1))) { // transfer done or ready
|
|
/* deal with tx */
|
|
handle_tx:
|
|
if (spi_status[port].tx_buf) {
|
|
if (spi_status[port].tx_pos >= spi_status[port].tx_len) {
|
|
/* we're done */
|
|
spi_status[port].tx_complete = 1;
|
|
spi_status[port].tx_buf = 0;
|
|
#if SPI_VERSION > 0
|
|
spi_status[port].shadow_spcon &= ~(1 << SPICON_IE_TX_SHIFT);
|
|
*regs->spcon = spi_status[port].shadow_spcon;
|
|
#endif
|
|
dprintf(DEBUG_SPI_SPEW, "spi_tx %d: tx done, pos %zu len %zu\n", port, spi_status[port].tx_pos, spi_status[port].tx_len);
|
|
} else {
|
|
uint32_t txfifo_left = SPI_FIFO_SIZE - BITS_SHIFT(status, SPSTA_TX_FIFO_BIT_HI, SPSTA_TX_FIFO_BIT_LO);
|
|
uint32_t to_transfer = spi_status[port].tx_len - spi_status[port].tx_pos;
|
|
|
|
to_transfer = __min(txfifo_left, to_transfer);
|
|
|
|
dprintf(DEBUG_SPI_SPEW, "spi_tx %d: txfifo_left %d, to_transfer %d, pos %zu, len %zu\n",
|
|
port, txfifo_left, to_transfer, spi_status[port].tx_pos, spi_status[port].tx_len);
|
|
|
|
for (i=0; i < to_transfer; i++)
|
|
*regs->sptdat = ((uint8_t *)spi_status[port].tx_buf)[spi_status[port].tx_pos + i];
|
|
|
|
spi_status[port].tx_pos += to_transfer;
|
|
}
|
|
}
|
|
|
|
/* look for rx */
|
|
handle_rx:
|
|
if (spi_status[port].rx_buf) {
|
|
uint32_t tmp;
|
|
uint32_t rxfifo = BITS_SHIFT(status, SPSTA_RX_FIFO_BIT_HI, SPSTA_RX_FIFO_BIT_LO);
|
|
uint32_t to_transfer = spi_status[port].rx_len - spi_status[port].rx_pos;
|
|
|
|
to_transfer = __min(rxfifo, to_transfer);
|
|
|
|
dprintf(DEBUG_SPI_SPEW, "spi_rx %d: fifo %d, to_transfer %d, pos %zu, len %zu\n",
|
|
port, rxfifo, to_transfer, spi_status[port].rx_pos, spi_status[port].rx_len);
|
|
|
|
for (i=0; i < to_transfer; i++) {
|
|
tmp = *regs->sprdat;
|
|
// dprintf(DEBUG_SPI_SPEW, "sprdat: 0x%x\n",tmp);
|
|
((uint8_t *)spi_status[port].rx_buf)[spi_status[port].rx_pos + i] = tmp; //*regs->sprdat;
|
|
}
|
|
|
|
spi_status[port].rx_pos += to_transfer;
|
|
|
|
if (spi_status[port].rx_pos >= spi_status[port].rx_len) {
|
|
spi_status[port].rx_complete = 1;
|
|
spi_status[port].rx_buf = 0;
|
|
spi_status[port].shadow_spcon &= ~(1 << SPICON_IE_RX_SHIFT);
|
|
*regs->spcon = spi_status[port].shadow_spcon;
|
|
//Hm, so RX finished, is it possible that TX is finished aswell?
|
|
goto handle_tx;
|
|
}
|
|
|
|
#if !SPI_AGD_MODE
|
|
if (spi_status[port].tx_buf == 0) {
|
|
uint32_t txfifo_left = SPI_FIFO_SIZE - BITS_SHIFT(status, SPSTA_TX_FIFO_BIT_HI, SPSTA_TX_FIFO_BIT_LO);
|
|
uint32_t cnt = spi_status[port].rx_len - spi_status[port].rx_pos;
|
|
if (cnt > txfifo_left) cnt = txfifo_left;
|
|
while (cnt--) *regs->sptdat = 0xff;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if ((spi_status[port].tx_buf == 0) && (spi_status[port].rx_buf == 0)) {
|
|
spi_status[port].shadow_spcon &= ~(1 << SPICON_IE_TR_SHIFT);
|
|
*regs->spcon = spi_status[port].shadow_spcon;
|
|
event_signal(&spi_status[port].event);
|
|
}
|
|
}
|
|
|
|
#if SPI_DMA
|
|
static void spi_dma_tx_handler(struct dma_xfer_t *xfer, int error)
|
|
{
|
|
struct spi_status_t *spi = xfer->user;
|
|
|
|
dprintf(DEBUG_SPI_SPEW, "spi_dma_tx_handler(): %d\n", error);
|
|
|
|
spi->tx_complete = 1;
|
|
}
|
|
|
|
static void spi_dma_rx_handler(struct dma_xfer_t *xfer, int error)
|
|
{
|
|
struct spi_status_t *spi = xfer->user;
|
|
|
|
dprintf(DEBUG_SPI_SPEW, "spi_dma_rx_handler(): %d\n", error);
|
|
|
|
spi->rx_complete = 1;
|
|
}
|
|
|
|
static void spi_cancel_dma(int port)
|
|
{
|
|
if(spi_status[port].dma) {
|
|
cancel_dma_transfers(spi_status[port].rx_dma);
|
|
cancel_dma_transfers(spi_status[port].tx_dma);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool spi_tx_complete(int port)
|
|
{
|
|
return spi_status[port].tx_complete;
|
|
}
|
|
|
|
bool spi_rx_complete(int port)
|
|
{
|
|
return spi_status[port].rx_complete;
|
|
}
|
|
|
|
int spi_write_etc(int port, const void *buf, size_t len, bool wait, bool with_rx)
|
|
{
|
|
const spi_regs_t *regs = spiregs(port);
|
|
uint32_t i;
|
|
int err = 0;
|
|
|
|
dprintf(DEBUG_SPI_SPEW, "spi_write(): port %d (%p) buf %p len %zu\n", port, &spi_status[port], buf, len);
|
|
|
|
if (regs == 0)
|
|
return -1;
|
|
|
|
/* clear the tx & rx fifos */
|
|
spi_flush_tx_fifo(port);
|
|
spi_flush_rx_fifo(port);
|
|
|
|
#if SPI_DMA
|
|
if (spi_status[port].dma) {
|
|
spi_status[port].tx_iov.iov_base = (void *)buf;
|
|
spi_status[port].tx_iov.iov_len = len;
|
|
spi_status[port].tx_xfer.dev_addr = (void *)regs->sptdat;
|
|
spi_status[port].tx_xfer.handler = spi_dma_tx_handler;
|
|
spi_status[port].tx_xfer.iov = &spi_status[port].tx_iov;
|
|
spi_status[port].tx_xfer.niov = 1;
|
|
spi_status[port].tx_xfer.user = &spi_status[port];
|
|
spi_status[port].tx_xfer.platform_flags = H1_DMA_FLAG__8BIT_TRANSFER | H1_DMA_FLAG__4_BURST_PERIPHERAL;
|
|
spi_status[port].tx_complete = 0;
|
|
|
|
if (!with_rx)
|
|
*regs->spcnt = 0;
|
|
|
|
spi_set_port_enable(port, true);
|
|
|
|
platform_cache_operation(CACHE_CLEAN | CACHE_INVALIDATE, 0, 0);
|
|
err = queue_dma_transfer(spi_status[port].tx_dma, &spi_status[port].tx_xfer);
|
|
if (err < 0) {
|
|
dprintf(DEBUG_CRITICAL, "error 0x%x queuing tx dma\n", err);
|
|
return -1;
|
|
}
|
|
|
|
if (wait) {
|
|
while (!spi_status[port].tx_complete || BITS_SHIFT(regs->spsta, SPSTA_TX_FIFO_BIT_HI, SPSTA_TX_FIFO_BIT_LO)) {
|
|
task_yield();
|
|
// dprintf(DEBUG_SPI_SPEW, "spsta 0x%x\n", *regs->spsta);
|
|
}
|
|
err = len;
|
|
}
|
|
} else {
|
|
#endif
|
|
spi_status[port].tx_buf = buf;
|
|
spi_status[port].tx_pos = __min(len, 8u);
|
|
spi_status[port].tx_len = len;
|
|
spi_status[port].tx_complete = 0;
|
|
spi_status[port].rx_buf = 0;
|
|
spi_status[port].rx_pos = 0;
|
|
spi_status[port].rx_len = 0;
|
|
|
|
event_unsignal(&spi_status[port].event);
|
|
|
|
if (with_rx) {
|
|
spi_status[port].shadow_spcon |= (1 << SPICON_IE_RX_SHIFT);
|
|
} else {
|
|
*regs->spcnt = 0;
|
|
}
|
|
|
|
for (i=0; i < spi_status[port].tx_pos; i++)
|
|
*regs->sptdat = ((uint8_t *)buf)[i];
|
|
|
|
#if SPI_VERSION > 0
|
|
*regs->sptdcnt = len;
|
|
spi_status[port].shadow_spcon |= (1 << SPICON_IE_TX_SHIFT);
|
|
#endif
|
|
|
|
spi_status[port].shadow_spcon |= (1 << SPICON_IE_TR_SHIFT);
|
|
*regs->spcon = spi_status[port].shadow_spcon;
|
|
|
|
spi_set_port_enable(port, true);
|
|
|
|
if (wait) {
|
|
while (!spi_status[port].tx_complete) {
|
|
event_wait(&spi_status[port].event);
|
|
}
|
|
|
|
while (BITS_SHIFT(*regs->spsta, SPSTA_TX_FIFO_BIT_HI, SPSTA_TX_FIFO_BIT_LO));
|
|
|
|
err = len;
|
|
}
|
|
#if SPI_DMA
|
|
}
|
|
#endif
|
|
|
|
return err;
|
|
}
|
|
|
|
int spi_write(int port, const void *buf, size_t len)
|
|
{
|
|
return spi_write_etc(port, buf, len, true, false);
|
|
}
|
|
|
|
int spi_read_etc(int port, void *buf, size_t len, bool wait, bool with_tx)
|
|
{
|
|
const spi_regs_t *regs = spiregs(port);
|
|
bool enable_tx_int = false;
|
|
int err = 0;
|
|
|
|
dprintf(DEBUG_SPI_SPEW, "spi_read(): port %d (%p) buf %p len %zu\n", port, &spi_status[port], buf, len);
|
|
|
|
if (regs == 0)
|
|
return -1;
|
|
|
|
/* clear the tx & rx fifos */
|
|
spi_flush_tx_fifo(port);
|
|
spi_flush_rx_fifo(port);
|
|
|
|
#if SPI_DMA
|
|
if (spi_status[port].dma) {
|
|
spi_status[port].rx_iov.iov_base = (void *)buf;
|
|
spi_status[port].rx_iov.iov_len = len;
|
|
spi_status[port].rx_xfer.dev_addr = (void *)regs->sprdat;
|
|
spi_status[port].rx_xfer.handler = spi_dma_rx_handler;
|
|
spi_status[port].rx_xfer.iov = &spi_status[port].rx_iov;
|
|
spi_status[port].rx_xfer.niov = 1;
|
|
spi_status[port].rx_xfer.user = &spi_status[port];
|
|
spi_status[port].rx_xfer.platform_flags = H1_DMA_FLAG__8BIT_TRANSFER | H1_DMA_FLAG__4_BURST_PERIPHERAL;
|
|
spi_status[port].rx_complete = 0;
|
|
|
|
*regs->spcnt = len;
|
|
|
|
spi_set_port_enable(port, true);
|
|
|
|
if (!with_tx) {
|
|
spi_set_port_enable(port, true);
|
|
/* start the transmitter, which should start the receive */
|
|
spi_status[port].shadow_spcon |= (1 << SPICON_AGD_SHIFT); // set auto garbage bit
|
|
}
|
|
|
|
platform_cache_operation(CACHE_CLEAN | CACHE_INVALIDATE, 0, 0);
|
|
err = queue_dma_transfer(spi_status[port].rx_dma, &spi_status[port].rx_xfer);
|
|
if (err < 0) {
|
|
dprintf(DEBUG_CRITICAL, "error 0x%x queuing rx dma\n", err);
|
|
return -1;
|
|
}
|
|
|
|
if (wait) {
|
|
while (!spi_status[port].rx_complete) {
|
|
task_yield();
|
|
// dprintf(DEBUG_SPI_SPEW, "spsta 0x%x\n", *regs->spsta);
|
|
}
|
|
if (!with_tx) {
|
|
spi_status[port].shadow_spcon &= ~(1 << SPICON_AGD_SHIFT); // clear auto garbage bit
|
|
*regs->spcon = spi_status[port].shadow_spcon;
|
|
}
|
|
err = len;
|
|
}
|
|
} else {
|
|
#endif
|
|
spi_status[port].rx_buf = buf;
|
|
spi_status[port].rx_pos = 0;
|
|
spi_status[port].rx_len = len;
|
|
spi_status[port].rx_complete = 0;
|
|
spi_status[port].overrun_errors = 0;
|
|
spi_status[port].tx_buf = 0;
|
|
spi_status[port].tx_pos = 0;
|
|
spi_status[port].tx_len = 0;
|
|
|
|
event_unsignal(&spi_status[port].event);
|
|
|
|
*regs->spcnt = len;
|
|
|
|
spi_set_port_enable(port, true);
|
|
|
|
if (with_tx) {
|
|
enable_tx_int = true;
|
|
} else {
|
|
/* start the transmitter, which should start the receive */
|
|
#if SPI_AGD_MODE
|
|
spi_status[port].shadow_spcon |= (1 << SPICON_AGD_SHIFT); // set auto garbage bit
|
|
#else
|
|
int cnt = spi_status[port].rx_len - spi_status[port].rx_pos;
|
|
if (cnt > SPI_FIFO_SIZE) cnt = SPI_FIFO_SIZE;
|
|
while (cnt--) *regs->sptdat = 0xff;
|
|
enable_tx_int = true;
|
|
#endif
|
|
}
|
|
|
|
#if SPI_VERSION > 0
|
|
if (enable_tx_int) {
|
|
*regs->sptdcnt = len;
|
|
spi_status[port].shadow_spcon |= (1 << SPICON_IE_TX_SHIFT);
|
|
}
|
|
#endif
|
|
|
|
spi_status[port].shadow_spcon |= (1 << SPICON_IE_RX_SHIFT) | (1 << SPICON_IE_TR_SHIFT);
|
|
*regs->spcon = spi_status[port].shadow_spcon;
|
|
|
|
if (wait) {
|
|
while (!spi_status[port].rx_complete) {
|
|
event_wait(&spi_status[port].event);
|
|
}
|
|
if (!with_tx) {
|
|
spi_status[port].shadow_spcon &= ~(1 << SPICON_AGD_SHIFT); // clear auto garbage bit
|
|
*regs->spcon = spi_status[port].shadow_spcon;
|
|
}
|
|
|
|
err = len;
|
|
}
|
|
#if SPI_DMA
|
|
}
|
|
#endif
|
|
|
|
return err;
|
|
}
|
|
|
|
int spi_read(int port, void *buf, size_t len)
|
|
{
|
|
return spi_read_etc(port, buf, len, true, false);
|
|
}
|
|
|
|
void spi_init(void)
|
|
{
|
|
uint32_t port;
|
|
const spi_regs_t *regs;
|
|
|
|
dprintf(DEBUG_CRITICAL, "spi_init()\n");
|
|
|
|
/* clear out the spi structure */
|
|
bzero(spi_status, sizeof(spi_status));
|
|
|
|
for (port = 0; port < SPIS_COUNT; port++) {
|
|
regs = spiregs(port);
|
|
if (!regs)
|
|
continue;
|
|
|
|
event_init(&spi_status[port].event, EVENT_FLAG_AUTO_UNSIGNAL, false);
|
|
|
|
/* make sure the clock is on for this device */
|
|
clock_gate(regs->clock, true);
|
|
|
|
/* default clock source to NCLK */
|
|
spi_status[port].clk = NCLK;
|
|
|
|
/* turn off all the spi ports */
|
|
spi_set_port_enable(port, false);
|
|
|
|
#if SPI_DMA
|
|
/* start off in interrupt driven mode */
|
|
spi_status[port].tx_dma = spi_status[port].rx_dma = -1;
|
|
spi_set_port_dma(port, false);
|
|
#endif
|
|
|
|
/* register interrupt handlers */
|
|
install_int_handler(regs->irq, &spi_interrupt, (void *)port);
|
|
unmask_int(regs->irq);
|
|
}
|
|
}
|
|
|
|
uint32_t spi_gpio_read(int port)
|
|
{
|
|
const spi_regs_t *regs = spiregs(port);
|
|
|
|
if (regs == 0)
|
|
return 0;
|
|
|
|
return (*regs->sppin >> 1) & 1;
|
|
}
|
|
|
|
void spi_gpio_write(int port, uint32_t val)
|
|
{
|
|
const spi_regs_t *regs = spiregs(port);
|
|
|
|
if (regs == 0)
|
|
return;
|
|
|
|
if (val) *regs->sppin |= (1 << 1);
|
|
else *regs->sppin &= ~(1 << 1);
|
|
}
|
|
|
|
void spi_gpio_configure(int port, uint32_t config)
|
|
{
|
|
const spi_regs_t *regs = spiregs(port);
|
|
|
|
if (regs == 0)
|
|
return;
|
|
|
|
switch (config) {
|
|
case GPIO_CFG_OUT_0 :
|
|
*regs->sppin &= ~(1 << 1);
|
|
break;
|
|
|
|
case GPIO_CFG_OUT_1 :
|
|
*regs->sppin |= (1 << 1);
|
|
break;
|
|
}
|
|
}
|