| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) ST-Ericsson AB 2010 |
| * Author: Daniel Martensson |
| */ |
| #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, |
| }, |
| }; |