/* * Copyright (C) ST-Ericsson AB 2010 * Author: Daniel Martensson * License terms: GNU General Public License (GPL) version 2. */ #include <linux/init.h> #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, }, };