You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
6.4 KiB
254 lines
6.4 KiB
/* |
|
* Copyright (C) ST-Ericsson AB 2010 |
|
* Author: Daniel Martensson |
|
* License terms: GNU General Public License (GPL) version 2. |
|
*/ |
|
#include <linux/module.h> |
|
#include <linux/device.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/string.h> |
|
#include <linux/semaphore.h> |
|
#include <linux/workqueue.h> |
|
#include <linux/completion.h> |
|
#include <linux/list.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/dma-mapping.h> |
|
#include <linux/delay.h> |
|
#include <linux/sched.h> |
|
#include <linux/debugfs.h> |
|
#include <net/caif/caif_spi.h> |
|
|
|
#ifndef CONFIG_CAIF_SPI_SYNC |
|
#define SPI_DATA_POS 0 |
|
static inline int forward_to_spi_cmd(struct cfspi *cfspi) |
|
{ |
|
return cfspi->rx_cpck_len; |
|
} |
|
#else |
|
#define SPI_DATA_POS SPI_CMD_SZ |
|
static inline int forward_to_spi_cmd(struct cfspi *cfspi) |
|
{ |
|
return 0; |
|
} |
|
#endif |
|
|
|
int spi_frm_align = 2; |
|
|
|
/* |
|
* SPI padding options. |
|
* Warning: must be a base of 2 (& operation used) and can not be zero ! |
|
*/ |
|
int spi_up_head_align = 1 << 1; |
|
int spi_up_tail_align = 1 << 0; |
|
int spi_down_head_align = 1 << 2; |
|
int spi_down_tail_align = 1 << 1; |
|
|
|
#ifdef CONFIG_DEBUG_FS |
|
static inline void debugfs_store_prev(struct cfspi *cfspi) |
|
{ |
|
/* Store previous command for debugging reasons.*/ |
|
cfspi->pcmd = cfspi->cmd; |
|
/* Store previous transfer. */ |
|
cfspi->tx_ppck_len = cfspi->tx_cpck_len; |
|
cfspi->rx_ppck_len = cfspi->rx_cpck_len; |
|
} |
|
#else |
|
static inline void debugfs_store_prev(struct cfspi *cfspi) |
|
{ |
|
} |
|
#endif |
|
|
|
void cfspi_xfer(struct work_struct *work) |
|
{ |
|
struct cfspi *cfspi; |
|
u8 *ptr = NULL; |
|
unsigned long flags; |
|
int ret; |
|
cfspi = container_of(work, struct cfspi, work); |
|
|
|
/* Initialize state. */ |
|
cfspi->cmd = SPI_CMD_EOT; |
|
|
|
for (;;) { |
|
|
|
cfspi_dbg_state(cfspi, CFSPI_STATE_WAITING); |
|
|
|
/* Wait for master talk or transmit event. */ |
|
wait_event_interruptible(cfspi->wait, |
|
test_bit(SPI_XFER, &cfspi->state) || |
|
test_bit(SPI_TERMINATE, &cfspi->state)); |
|
|
|
if (test_bit(SPI_TERMINATE, &cfspi->state)) |
|
return; |
|
|
|
#if CFSPI_DBG_PREFILL |
|
/* Prefill buffers for easier debugging. */ |
|
memset(cfspi->xfer.va_tx, 0xFF, SPI_DMA_BUF_LEN); |
|
memset(cfspi->xfer.va_rx, 0xFF, SPI_DMA_BUF_LEN); |
|
#endif /* CFSPI_DBG_PREFILL */ |
|
|
|
cfspi_dbg_state(cfspi, CFSPI_STATE_AWAKE); |
|
|
|
/* Check whether we have a committed frame. */ |
|
if (cfspi->tx_cpck_len) { |
|
int len; |
|
|
|
cfspi_dbg_state(cfspi, CFSPI_STATE_FETCH_PKT); |
|
|
|
/* Copy committed SPI frames after the SPI indication. */ |
|
ptr = (u8 *) cfspi->xfer.va_tx; |
|
ptr += SPI_IND_SZ; |
|
len = cfspi_xmitfrm(cfspi, ptr, cfspi->tx_cpck_len); |
|
WARN_ON(len != cfspi->tx_cpck_len); |
|
} |
|
|
|
cfspi_dbg_state(cfspi, CFSPI_STATE_GET_NEXT); |
|
|
|
/* Get length of next frame to commit. */ |
|
cfspi->tx_npck_len = cfspi_xmitlen(cfspi); |
|
|
|
WARN_ON(cfspi->tx_npck_len > SPI_DMA_BUF_LEN); |
|
|
|
/* |
|
* Add indication and length at the beginning of the frame, |
|
* using little endian. |
|
*/ |
|
ptr = (u8 *) cfspi->xfer.va_tx; |
|
*ptr++ = SPI_CMD_IND; |
|
*ptr++ = (SPI_CMD_IND & 0xFF00) >> 8; |
|
*ptr++ = cfspi->tx_npck_len & 0x00FF; |
|
*ptr++ = (cfspi->tx_npck_len & 0xFF00) >> 8; |
|
|
|
/* Calculate length of DMAs. */ |
|
cfspi->xfer.tx_dma_len = cfspi->tx_cpck_len + SPI_IND_SZ; |
|
cfspi->xfer.rx_dma_len = cfspi->rx_cpck_len + SPI_CMD_SZ; |
|
|
|
/* Add SPI TX frame alignment padding, if necessary. */ |
|
if (cfspi->tx_cpck_len && |
|
(cfspi->xfer.tx_dma_len % spi_frm_align)) { |
|
|
|
cfspi->xfer.tx_dma_len += spi_frm_align - |
|
(cfspi->xfer.tx_dma_len % spi_frm_align); |
|
} |
|
|
|
/* Add SPI RX frame alignment padding, if necessary. */ |
|
if (cfspi->rx_cpck_len && |
|
(cfspi->xfer.rx_dma_len % spi_frm_align)) { |
|
|
|
cfspi->xfer.rx_dma_len += spi_frm_align - |
|
(cfspi->xfer.rx_dma_len % spi_frm_align); |
|
} |
|
|
|
cfspi_dbg_state(cfspi, CFSPI_STATE_INIT_XFER); |
|
|
|
/* Start transfer. */ |
|
ret = cfspi->dev->init_xfer(&cfspi->xfer, cfspi->dev); |
|
WARN_ON(ret); |
|
|
|
cfspi_dbg_state(cfspi, CFSPI_STATE_WAIT_ACTIVE); |
|
|
|
/* |
|
* TODO: We might be able to make an assumption if this is the |
|
* first loop. Make sure that minimum toggle time is respected. |
|
*/ |
|
udelay(MIN_TRANSITION_TIME_USEC); |
|
|
|
cfspi_dbg_state(cfspi, CFSPI_STATE_SIG_ACTIVE); |
|
|
|
/* Signal that we are ready to receive data. */ |
|
cfspi->dev->sig_xfer(true, cfspi->dev); |
|
|
|
cfspi_dbg_state(cfspi, CFSPI_STATE_WAIT_XFER_DONE); |
|
|
|
/* Wait for transfer completion. */ |
|
wait_for_completion(&cfspi->comp); |
|
|
|
cfspi_dbg_state(cfspi, CFSPI_STATE_XFER_DONE); |
|
|
|
if (cfspi->cmd == SPI_CMD_EOT) { |
|
/* |
|
* Clear the master talk bit. A xfer is always at |
|
* least two bursts. |
|
*/ |
|
clear_bit(SPI_SS_ON, &cfspi->state); |
|
} |
|
|
|
cfspi_dbg_state(cfspi, CFSPI_STATE_WAIT_INACTIVE); |
|
|
|
/* Make sure that the minimum toggle time is respected. */ |
|
if (SPI_XFER_TIME_USEC(cfspi->xfer.tx_dma_len, |
|
cfspi->dev->clk_mhz) < |
|
MIN_TRANSITION_TIME_USEC) { |
|
|
|
udelay(MIN_TRANSITION_TIME_USEC - |
|
SPI_XFER_TIME_USEC |
|
(cfspi->xfer.tx_dma_len, cfspi->dev->clk_mhz)); |
|
} |
|
|
|
cfspi_dbg_state(cfspi, CFSPI_STATE_SIG_INACTIVE); |
|
|
|
/* De-assert transfer signal. */ |
|
cfspi->dev->sig_xfer(false, cfspi->dev); |
|
|
|
/* Check whether we received a CAIF packet. */ |
|
if (cfspi->rx_cpck_len) { |
|
int len; |
|
|
|
cfspi_dbg_state(cfspi, CFSPI_STATE_DELIVER_PKT); |
|
|
|
/* Parse SPI frame. */ |
|
ptr = ((u8 *)(cfspi->xfer.va_rx + SPI_DATA_POS)); |
|
|
|
len = cfspi_rxfrm(cfspi, ptr, cfspi->rx_cpck_len); |
|
WARN_ON(len != cfspi->rx_cpck_len); |
|
} |
|
|
|
/* Check the next SPI command and length. */ |
|
ptr = (u8 *) cfspi->xfer.va_rx; |
|
|
|
ptr += forward_to_spi_cmd(cfspi); |
|
|
|
cfspi->cmd = *ptr++; |
|
cfspi->cmd |= ((*ptr++) << 8) & 0xFF00; |
|
cfspi->rx_npck_len = *ptr++; |
|
cfspi->rx_npck_len |= ((*ptr++) << 8) & 0xFF00; |
|
|
|
WARN_ON(cfspi->rx_npck_len > SPI_DMA_BUF_LEN); |
|
WARN_ON(cfspi->cmd > SPI_CMD_EOT); |
|
|
|
debugfs_store_prev(cfspi); |
|
|
|
/* Check whether the master issued an EOT command. */ |
|
if (cfspi->cmd == SPI_CMD_EOT) { |
|
/* Reset state. */ |
|
cfspi->tx_cpck_len = 0; |
|
cfspi->rx_cpck_len = 0; |
|
} else { |
|
/* Update state. */ |
|
cfspi->tx_cpck_len = cfspi->tx_npck_len; |
|
cfspi->rx_cpck_len = cfspi->rx_npck_len; |
|
} |
|
|
|
/* |
|
* Check whether we need to clear the xfer bit. |
|
* Spin lock needed for packet insertion. |
|
* Test and clear of different bits |
|
* are not supported. |
|
*/ |
|
spin_lock_irqsave(&cfspi->lock, flags); |
|
if (cfspi->cmd == SPI_CMD_EOT && !cfspi_xmitlen(cfspi) |
|
&& !test_bit(SPI_SS_ON, &cfspi->state)) |
|
clear_bit(SPI_XFER, &cfspi->state); |
|
|
|
spin_unlock_irqrestore(&cfspi->lock, flags); |
|
} |
|
} |
|
|
|
struct platform_driver cfspi_spi_driver = { |
|
.probe = cfspi_spi_probe, |
|
.remove = cfspi_spi_remove, |
|
.driver = { |
|
.name = "cfspi_sspi", |
|
.owner = THIS_MODULE, |
|
}, |
|
};
|
|
|