freebsd-dev/sys/dev/qcom_qup/qcom_spi_hw.c
Adrian Chadd d27ba30884 qcom_qup: add initial v1/v2 QUP SPI driver
The Qualcomm Universal Peripherals Engine (QUP) is a unified SPI and I2C
peripheral that ships with a variety of Qualcomm SoCs.

It supports three transfer modes - single PIO, block PIO and DMA.

This driver only supports the single PIO mode, which is enough to
bootstrap the rest of the SPI NAND/NOR support and means I can do
things like read the Wifi calibration data from NOR.  It has some
hardware support code for the other transfer modes as well as
some support for split transfers (ie, transfers with no read or
write phase), but I haven't yet implemented those.

This driver is based on four sources - the linux driver, the u-boot
driver, some initial work done for APQ8064 by mmel@, and the APQ8064
Technical Reference Manual which is surprisingly free and open to
read.  The linux and u-boot drivers approach a variety of things
completely differently, from how PIO is done, the hardware support
for re-ordering bytes in a transfer word and how the CS lines
are used.

Tested:

* IPQ4018, SPI to NAND/NOR flash, PIO only
2021-12-27 15:27:29 -08:00

986 lines
25 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2021, Adrian Chadd <adrian@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice unmodified, this list of conditions, and the following
* disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/interrupt.h>
#include <sys/malloc.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <vm/vm_extern.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <dev/gpio/gpiobusvar.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/extres/clk/clk.h>
#include <dev/extres/hwreset/hwreset.h>
#include <dev/spibus/spi.h>
#include <dev/spibus/spibusvar.h>
#include "spibus_if.h"
#include <dev/qcom_qup/qcom_spi_var.h>
#include <dev/qcom_qup/qcom_spi_reg.h>
#include <dev/qcom_qup/qcom_qup_reg.h>
#include <dev/qcom_qup/qcom_spi_debug.h>
int
qcom_spi_hw_read_controller_transfer_sizes(struct qcom_spi_softc *sc)
{
uint32_t reg, val;
reg = QCOM_SPI_READ_4(sc, QUP_IO_M_MODES);
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_TRANSFER_SETUP,
"%s: QUP_IO_M_MODES=0x%08x\n", __func__, reg);
/* Input block size */
val = (reg >> QUP_IO_M_INPUT_BLOCK_SIZE_SHIFT)
& QUP_IO_M_INPUT_BLOCK_SIZE_MASK;
if (val == 0)
sc->config.input_block_size = 4;
else
sc->config.input_block_size = val * 16;
/* Output block size */
val = (reg >> QUP_IO_M_OUTPUT_BLOCK_SIZE_SHIFT)
& QUP_IO_M_OUTPUT_BLOCK_SIZE_MASK;
if (val == 0)
sc->config.output_block_size = 4;
else
sc->config.output_block_size = val * 16;
/* Input FIFO size */
val = (reg >> QUP_IO_M_INPUT_FIFO_SIZE_SHIFT)
& QUP_IO_M_INPUT_FIFO_SIZE_MASK;
sc->config.input_fifo_size =
sc->config.input_block_size * (2 << val);
/* Output FIFO size */
val = (reg >> QUP_IO_M_OUTPUT_FIFO_SIZE_SHIFT)
& QUP_IO_M_OUTPUT_FIFO_SIZE_MASK;
sc->config.output_fifo_size =
sc->config.output_block_size * (2 << val);
return (0);
}
static bool
qcom_spi_hw_qup_is_state_valid_locked(struct qcom_spi_softc *sc)
{
uint32_t reg;
QCOM_SPI_ASSERT_LOCKED(sc);
reg = QCOM_SPI_READ_4(sc, QUP_STATE);
QCOM_SPI_BARRIER_READ(sc);
return !! (reg & QUP_STATE_VALID);
}
static int
qcom_spi_hw_qup_wait_state_valid_locked(struct qcom_spi_softc *sc)
{
int i;
for (i = 0; i < 10; i++) {
if (qcom_spi_hw_qup_is_state_valid_locked(sc))
break;
}
if (i >= 10) {
device_printf(sc->sc_dev,
"ERROR: timeout waiting for valid state\n");
return (ENXIO);
}
return (0);
}
static bool
qcom_spi_hw_is_opmode_dma_locked(struct qcom_spi_softc *sc)
{
QCOM_SPI_ASSERT_LOCKED(sc);
if (sc->state.transfer_mode == QUP_IO_M_MODE_DMOV)
return (true);
if (sc->state.transfer_mode == QUP_IO_M_MODE_BAM)
return (true);
return (false);
}
int
qcom_spi_hw_qup_set_state_locked(struct qcom_spi_softc *sc, uint32_t state)
{
uint32_t cur_state;
int ret;
QCOM_SPI_ASSERT_LOCKED(sc);
/* Wait until the state becomes valid */
ret = qcom_spi_hw_qup_wait_state_valid_locked(sc);
if (ret != 0) {
return (ret);
}
cur_state = QCOM_SPI_READ_4(sc, QUP_STATE);
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_STATE_CHANGE,
"%s: target state=%d, cur_state=0x%08x\n",
__func__, state, cur_state);
/*
* According to the QUP specification, when going
* from PAUSE to RESET, two writes are required.
*/
if ((state == QUP_STATE_RESET)
&& ((cur_state & QUP_STATE_MASK) == QUP_STATE_PAUSE)) {
QCOM_SPI_WRITE_4(sc, QUP_STATE, QUP_STATE_CLEAR);
QCOM_SPI_BARRIER_WRITE(sc);
QCOM_SPI_WRITE_4(sc, QUP_STATE, QUP_STATE_CLEAR);
QCOM_SPI_BARRIER_WRITE(sc);
} else {
cur_state &= ~QUP_STATE_MASK;
cur_state |= state;
QCOM_SPI_WRITE_4(sc, QUP_STATE, cur_state);
QCOM_SPI_BARRIER_WRITE(sc);
}
/* Wait until the state becomes valid */
ret = qcom_spi_hw_qup_wait_state_valid_locked(sc);
if (ret != 0) {
return (ret);
}
cur_state = QCOM_SPI_READ_4(sc, QUP_STATE);
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_STATE_CHANGE,
"%s: FINISH: target state=%d, cur_state=0x%08x\n",
__func__, state, cur_state);
return (0);
}
/*
* Do initial QUP setup.
*
* This is initially for the SPI driver; it would be interesting to see how
* much of this is the same with the I2C/HSUART paths.
*/
int
qcom_spi_hw_qup_init_locked(struct qcom_spi_softc *sc)
{
int ret;
QCOM_SPI_ASSERT_LOCKED(sc);
/* Full hardware reset */
(void) qcom_spi_hw_do_full_reset(sc);
ret = qcom_spi_hw_qup_set_state_locked(sc, QUP_STATE_RESET);
if (ret != 0) {
device_printf(sc->sc_dev, "ERROR: %s: couldn't reset\n",
__func__);
goto error;
}
QCOM_SPI_WRITE_4(sc, QUP_OPERATIONAL, 0);
QCOM_SPI_WRITE_4(sc, QUP_IO_M_MODES, 0);
/* Note: no QUP_OPERATIONAL_MASK in QUP v1 */
if (! QCOM_SPI_QUP_VERSION_V1(sc))
QCOM_SPI_WRITE_4(sc, QUP_OPERATIONAL_MASK, 0);
/* Explicitly disable input overrun in QUP v1 */
if (QCOM_SPI_QUP_VERSION_V1(sc))
QCOM_SPI_WRITE_4(sc, QUP_ERROR_FLAGS_EN,
QUP_ERROR_OUTPUT_OVER_RUN
| QUP_ERROR_INPUT_UNDER_RUN
| QUP_ERROR_OUTPUT_UNDER_RUN);
QCOM_SPI_BARRIER_WRITE(sc);
return (0);
error:
return (ret);
}
/*
* Do initial SPI setup.
*/
int
qcom_spi_hw_spi_init_locked(struct qcom_spi_softc *sc)
{
QCOM_SPI_ASSERT_LOCKED(sc);
/* Initial SPI error flags */
QCOM_SPI_WRITE_4(sc, SPI_ERROR_FLAGS_EN,
QUP_ERROR_INPUT_UNDER_RUN
| QUP_ERROR_OUTPUT_UNDER_RUN);
QCOM_SPI_BARRIER_WRITE(sc);
/* Initial SPI config */
QCOM_SPI_WRITE_4(sc, SPI_CONFIG, 0);
QCOM_SPI_BARRIER_WRITE(sc);
/* Initial CS/tri-state io control config */
QCOM_SPI_WRITE_4(sc, SPI_IO_CONTROL,
SPI_IO_C_NO_TRI_STATE
| SPI_IO_C_CS_SELECT(sc->config.cs_select));
QCOM_SPI_BARRIER_WRITE(sc);
return (0);
}
/*
* Force the currently selected device CS line to be active
* or inactive.
*
* This forces it to be active or inactive rather than letting
* the SPI transfer machine do its thing. If you want to be able
* break up a big transaction into a handful of smaller ones,
* without toggling /CS_n for that device, then you need it forced.
* (If you toggle the /CS_n to the device to inactive then active,
* NOR/NAND devices tend to stop a block transfer.)
*/
int
qcom_spi_hw_spi_cs_force(struct qcom_spi_softc *sc, int cs, bool enable)
{
uint32_t reg;
QCOM_SPI_ASSERT_LOCKED(sc);
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_CHIPSELECT,
"%s: called, enable=%u\n",
__func__, enable);
reg = QCOM_SPI_READ_4(sc, SPI_IO_CONTROL);
if (enable)
reg |= SPI_IO_C_FORCE_CS;
else
reg &= ~SPI_IO_C_FORCE_CS;
reg &= ~SPI_IO_C_CS_SELECT_MASK;
reg |= SPI_IO_C_CS_SELECT(cs);
QCOM_SPI_WRITE_4(sc, SPI_IO_CONTROL, reg);
QCOM_SPI_BARRIER_WRITE(sc);
return (0);
}
/*
* ACK/store current interrupt flag state.
*/
int
qcom_spi_hw_interrupt_handle(struct qcom_spi_softc *sc)
{
uint32_t qup_error, spi_error, op_flags;
QCOM_SPI_ASSERT_LOCKED(sc);
/* Get QUP/SPI state */
qup_error = QCOM_SPI_READ_4(sc, QUP_ERROR_FLAGS);
spi_error = QCOM_SPI_READ_4(sc, SPI_ERROR_FLAGS);
op_flags = QCOM_SPI_READ_4(sc, QUP_OPERATIONAL);
/* ACK state */
QCOM_SPI_WRITE_4(sc, QUP_ERROR_FLAGS, qup_error);
QCOM_SPI_WRITE_4(sc, SPI_ERROR_FLAGS, spi_error);
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_INTR,
"%s: called; qup=0x%08x, spi=0x%08x, op=0x%08x\n",
__func__,
qup_error,
spi_error,
op_flags);
/* handle error flags */
if (qup_error != 0) {
device_printf(sc->sc_dev, "ERROR: (QUP) mask=0x%08x\n",
qup_error);
sc->intr.error = true;
}
if (spi_error != 0) {
device_printf(sc->sc_dev, "ERROR: (SPI) mask=0x%08x\n",
spi_error);
sc->intr.error = true;
}
/* handle operational state */
if (qcom_spi_hw_is_opmode_dma_locked(sc)) {
/* ACK interrupts now */
QCOM_SPI_WRITE_4(sc, QUP_OPERATIONAL, op_flags);
if ((op_flags & QUP_OP_IN_SERVICE_FLAG)
&& (op_flags & QUP_OP_MAX_INPUT_DONE_FLAG))
sc->intr.rx_dma_done = true;
if ((op_flags & QUP_OP_OUT_SERVICE_FLAG)
&& (op_flags & QUP_OP_MAX_OUTPUT_DONE_FLAG))
sc->intr.tx_dma_done = true;
} else {
/* FIFO/Block */
if (op_flags & QUP_OP_IN_SERVICE_FLAG)
sc->intr.do_rx = true;
if (op_flags & QUP_OP_OUT_SERVICE_FLAG)
sc->intr.do_tx = true;
}
/* Check if we've finished transfers */
if (op_flags & QUP_OP_MAX_INPUT_DONE_FLAG)
sc->intr.done = true;
if (sc->intr.error)
sc->intr.done = true;
return (0);
}
/*
* Make initial transfer selections based on the transfer sizes
* and alignment.
*
* For now this'll just default to FIFO until that works, and then
* will grow to include BLOCK / DMA as appropriate.
*/
int
qcom_spi_hw_setup_transfer_selection(struct qcom_spi_softc *sc, uint32_t len)
{
QCOM_SPI_ASSERT_LOCKED(sc);
/*
* For now only support doing a single FIFO transfer.
* The main PIO transfer routine loop will break it up for us.
*/
sc->state.transfer_mode = QUP_IO_M_MODE_FIFO;
sc->transfer.tx_offset = 0;
sc->transfer.rx_offset = 0;
sc->transfer.tx_len = 0;
sc->transfer.rx_len = 0;
sc->transfer.tx_buf = NULL;
sc->transfer.rx_buf = NULL;
/*
* If we're sending a DWORD multiple sized block (like IO buffers)
* then we can totally just use the DWORD size transfers.
*
* This is really only valid for PIO/block modes; I'm not yet
* sure what we should do for DMA modes.
*/
if (len > 0 && len % 4 == 0)
sc->state.transfer_word_size = 4;
else
sc->state.transfer_word_size = 1;
return (0);
}
/*
* Blank the transfer state after a full transfer is completed.
*/
int
qcom_spi_hw_complete_transfer(struct qcom_spi_softc *sc)
{
QCOM_SPI_ASSERT_LOCKED(sc);
sc->state.transfer_mode = QUP_IO_M_MODE_FIFO;
sc->transfer.tx_offset = 0;
sc->transfer.rx_offset = 0;
sc->transfer.tx_len = 0;
sc->transfer.rx_len = 0;
sc->transfer.tx_buf = NULL;
sc->transfer.rx_buf = NULL;
sc->state.transfer_word_size = 0;
return (0);
}
/*
* Configure up the transfer selection for the current transfer.
*
* This calculates how many words we can transfer in the current
* transfer and what's left to transfer.
*/
int
qcom_spi_hw_setup_current_transfer(struct qcom_spi_softc *sc)
{
uint32_t bytes_left;
QCOM_SPI_ASSERT_LOCKED(sc);
/*
* XXX For now, base this on the TX side buffer size, not both.
* Later on we'll want to configure it based on the MAX of
* either and just eat up the dummy values in the PIO
* routines. (For DMA it's .. more annoyingly complicated
* if the transfer sizes are not symmetrical.)
*/
bytes_left = sc->transfer.tx_len - sc->transfer.tx_offset;
if (sc->state.transfer_mode == QUP_IO_M_MODE_FIFO) {
/*
* For FIFO transfers the num_words limit depends upon
* the word size, FIFO size and how many bytes are left.
* It definitely will be under SPI_MAX_XFER so don't
* worry about that here.
*/
sc->transfer.num_words = bytes_left / sc->state.transfer_word_size;
sc->transfer.num_words = MIN(sc->transfer.num_words,
sc->config.input_fifo_size / sizeof(uint32_t));
} else if (sc->state.transfer_mode == QUP_IO_M_MODE_BLOCK) {
/*
* For BLOCK transfers the logic will be a little different.
* Instead of it being based on the maximum input_fifo_size,
* it'll be broken down into the 'words per block" size but
* our maximum transfer size will ACTUALLY be capped by
* SPI_MAX_XFER (65536-64 bytes.) Each transfer
* will end up being in multiples of a block until the
* last transfer.
*/
sc->transfer.num_words = bytes_left / sc->state.transfer_word_size;
sc->transfer.num_words = MIN(sc->transfer.num_words,
SPI_MAX_XFER);
}
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_TRANSFER_SETUP,
"%s: transfer.tx_len=%u,"
"transfer.tx_offset=%u,"
" transfer_word_size=%u,"
" bytes_left=%u, num_words=%u, fifo_word_max=%u\n",
__func__,
sc->transfer.tx_len,
sc->transfer.tx_offset,
sc->state.transfer_word_size,
bytes_left,
sc->transfer.num_words,
sc->config.input_fifo_size / sizeof(uint32_t));
return (0);
}
/*
* Setup the PIO FIFO transfer count.
*
* Note that we get a /single/ TX/RX phase up to these num_words
* transfers.
*/
int
qcom_spi_hw_setup_pio_transfer_cnt(struct qcom_spi_softc *sc)
{
QCOM_SPI_ASSERT_LOCKED(sc);
QCOM_SPI_WRITE_4(sc, QUP_MX_READ_CNT, sc->transfer.num_words);
QCOM_SPI_WRITE_4(sc, QUP_MX_WRITE_CNT, sc->transfer.num_words);
QCOM_SPI_WRITE_4(sc, QUP_MX_INPUT_CNT, 0);
QCOM_SPI_WRITE_4(sc, QUP_MX_OUTPUT_CNT, 0);
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_TRANSFER_SETUP,
"%s: num_words=%u\n", __func__,
sc->transfer.num_words);
QCOM_SPI_BARRIER_WRITE(sc);
return (0);
}
/*
* Setup the PIO BLOCK transfer count.
*
* This sets up the total transfer size, in TX/RX FIFO block size
* chunks. We will get multiple notifications when a block sized
* chunk of data is avaliable or required.
*/
int
qcom_spi_hw_setup_block_transfer_cnt(struct qcom_spi_softc *sc)
{
QCOM_SPI_ASSERT_LOCKED(sc);
QCOM_SPI_WRITE_4(sc, QUP_MX_READ_CNT, 0);
QCOM_SPI_WRITE_4(sc, QUP_MX_WRITE_CNT, 0);
QCOM_SPI_WRITE_4(sc, QUP_MX_INPUT_CNT, sc->transfer.num_words);
QCOM_SPI_WRITE_4(sc, QUP_MX_OUTPUT_CNT, sc->transfer.num_words);
QCOM_SPI_BARRIER_WRITE(sc);
return (0);
}
int
qcom_spi_hw_setup_io_modes(struct qcom_spi_softc *sc)
{
uint32_t reg;
QCOM_SPI_ASSERT_LOCKED(sc);
reg = QCOM_SPI_READ_4(sc, QUP_IO_M_MODES);
reg &= ~((QUP_IO_M_INPUT_MODE_MASK << QUP_IO_M_INPUT_MODE_SHIFT)
| (QUP_IO_M_OUTPUT_MODE_MASK << QUP_IO_M_OUTPUT_MODE_SHIFT));
/*
* If it's being done using DMA then the hardware will
* need to pack and unpack the byte stream into the word/dword
* stream being expected by the SPI/QUP micro engine.
*
* For PIO modes we're doing the pack/unpack in software,
* see the pio/block transfer routines.
*/
if (qcom_spi_hw_is_opmode_dma_locked(sc))
reg |= (QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN);
else
reg &= ~(QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN);
/* Transfer mode */
reg |= ((sc->state.transfer_mode & QUP_IO_M_INPUT_MODE_MASK)
<< QUP_IO_M_INPUT_MODE_SHIFT);
reg |= ((sc->state.transfer_mode & QUP_IO_M_OUTPUT_MODE_MASK)
<< QUP_IO_M_OUTPUT_MODE_SHIFT);
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_TRANSFER_SETUP,
"%s: QUP_IO_M_MODES=0x%08x\n", __func__, reg);
QCOM_SPI_WRITE_4(sc, QUP_IO_M_MODES, reg);
QCOM_SPI_BARRIER_WRITE(sc);
return (0);
}
int
qcom_spi_hw_setup_spi_io_clock_polarity(struct qcom_spi_softc *sc,
bool cpol)
{
uint32_t reg;
QCOM_SPI_ASSERT_LOCKED(sc);
reg = QCOM_SPI_READ_4(sc, SPI_IO_CONTROL);
if (cpol)
reg |= SPI_IO_C_CLK_IDLE_HIGH;
else
reg &= ~SPI_IO_C_CLK_IDLE_HIGH;
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_TRANSFER_SETUP,
"%s: SPI_IO_CONTROL=0x%08x\n", __func__, reg);
QCOM_SPI_WRITE_4(sc, SPI_IO_CONTROL, reg);
QCOM_SPI_BARRIER_WRITE(sc);
return (0);
}
int
qcom_spi_hw_setup_spi_config(struct qcom_spi_softc *sc, uint32_t clock_val,
bool cpha)
{
uint32_t reg;
/*
* For now we don't have a way to configure loopback SPI for testing,
* or the clock/transfer phase. When we do then here's where we
* would put that.
*/
QCOM_SPI_ASSERT_LOCKED(sc);
reg = QCOM_SPI_READ_4(sc, SPI_CONFIG);
reg &= ~SPI_CONFIG_LOOPBACK;
if (cpha)
reg &= ~SPI_CONFIG_INPUT_FIRST;
else
reg |= SPI_CONFIG_INPUT_FIRST;
/*
* If the frequency is above SPI_HS_MIN_RATE then enable high speed.
* This apparently improves stability.
*
* Note - don't do this if SPI loopback is enabled!
*/
if (clock_val >= SPI_HS_MIN_RATE)
reg |= SPI_CONFIG_HS_MODE;
else
reg &= ~SPI_CONFIG_HS_MODE;
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_TRANSFER_SETUP,
"%s: SPI_CONFIG=0x%08x\n", __func__, reg);
QCOM_SPI_WRITE_4(sc, SPI_CONFIG, reg);
QCOM_SPI_BARRIER_WRITE(sc);
return (0);
}
int
qcom_spi_hw_setup_qup_config(struct qcom_spi_softc *sc, bool is_tx, bool is_rx)
{
uint32_t reg;
QCOM_SPI_ASSERT_LOCKED(sc);
reg = QCOM_SPI_READ_4(sc, QUP_CONFIG);
reg &= ~(QUP_CONFIG_NO_INPUT | QUP_CONFIG_NO_OUTPUT | QUP_CONFIG_N);
/* SPI mode */
reg |= QUP_CONFIG_SPI_MODE;
/* bitmask for number of bits per word being used in each FIFO slot */
reg |= ((sc->state.transfer_word_size * 8) - 1) & QUP_CONFIG_N;
/*
* When doing DMA we need to configure whether we are shifting
* data in, out, and/or both. For PIO/block modes it must stay
* unset.
*/
if (qcom_spi_hw_is_opmode_dma_locked(sc)) {
if (is_rx == false)
reg |= QUP_CONFIG_NO_INPUT;
if (is_tx == false)
reg |= QUP_CONFIG_NO_OUTPUT;
}
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_TRANSFER_SETUP,
"%s: QUP_CONFIG=0x%08x\n", __func__, reg);
QCOM_SPI_WRITE_4(sc, QUP_CONFIG, reg);
QCOM_SPI_BARRIER_WRITE(sc);
return (0);
}
int
qcom_spi_hw_setup_operational_mask(struct qcom_spi_softc *sc)
{
QCOM_SPI_ASSERT_LOCKED(sc);
if (QCOM_SPI_QUP_VERSION_V1(sc)) {
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_TRANSFER_SETUP,
"%s: skipping, qupv1\n", __func__);
return (0);
}
if (qcom_spi_hw_is_opmode_dma_locked(sc))
QCOM_SPI_WRITE_4(sc, QUP_OPERATIONAL_MASK,
QUP_OP_IN_SERVICE_FLAG | QUP_OP_OUT_SERVICE_FLAG);
else
QCOM_SPI_WRITE_4(sc, QUP_OPERATIONAL_MASK, 0);
QCOM_SPI_BARRIER_WRITE(sc);
return (0);
}
/*
* ACK that we already have serviced the output FIFO.
*/
int
qcom_spi_hw_ack_write_pio_fifo(struct qcom_spi_softc *sc)
{
QCOM_SPI_ASSERT_LOCKED(sc);
QCOM_SPI_WRITE_4(sc, QUP_OPERATIONAL, QUP_OP_OUT_SERVICE_FLAG);
QCOM_SPI_BARRIER_WRITE(sc);
return (0);
}
int
qcom_spi_hw_ack_opmode(struct qcom_spi_softc *sc)
{
uint32_t reg;
QCOM_SPI_ASSERT_LOCKED(sc);
QCOM_SPI_BARRIER_READ(sc);
reg = QCOM_SPI_READ_4(sc, QUP_OPERATIONAL);
QCOM_SPI_WRITE_4(sc, QUP_OPERATIONAL, QUP_OP_OUT_SERVICE_FLAG);
QCOM_SPI_BARRIER_WRITE(sc);
return (0);
}
/*
* Read the value from the TX buffer into the given 32 bit DWORD,
* pre-shifting it into the place requested.
*
* Returns true if there was a byte available, false otherwise.
*/
static bool
qcom_spi_hw_write_from_tx_buf(struct qcom_spi_softc *sc, int shift,
uint32_t *val)
{
QCOM_SPI_ASSERT_LOCKED(sc);
if (sc->transfer.tx_buf == NULL)
return false;
if (sc->transfer.tx_offset < sc->transfer.tx_len) {
*val |= (sc->transfer.tx_buf[sc->transfer.tx_offset] & 0xff)
<< shift;
sc->transfer.tx_offset++;
return true;
}
return false;
}
int
qcom_spi_hw_write_pio_fifo(struct qcom_spi_softc *sc)
{
uint32_t i;
int num_bytes = 0;
QCOM_SPI_ASSERT_LOCKED(sc);
QCOM_SPI_WRITE_4(sc, QUP_OPERATIONAL, QUP_OP_OUT_SERVICE_FLAG);
QCOM_SPI_BARRIER_WRITE(sc);
/*
* Loop over the transfer num_words, do complain if we are full.
*/
for (i = 0; i < sc->transfer.num_words; i++) {
uint32_t reg;
/* Break if FIFO is full */
if ((QCOM_SPI_READ_4(sc, QUP_OPERATIONAL)
& QUP_OP_OUT_FIFO_FULL) != 0) {
device_printf(sc->sc_dev, "%s: FIFO full\n", __func__);
break;
}
/*
* Handle 1, 2, 4 byte transfer packing rules.
*
* Unlike read, where the shifting is done towards the MSB
* for us by default, we have to do it ourselves for transmit.
* There's a bit that one can set to do the preshifting
* (and u-boot uses it!) but I'll stick with what Linux is
* doing to make it easier for future maintenance.
*
* The format is the same as 4 byte RX - 0xaabbccdd;
* the byte ordering on the wire being aa, bb, cc, dd.
*/
reg = 0;
if (sc->state.transfer_word_size == 1) {
if (qcom_spi_hw_write_from_tx_buf(sc, 24, &reg))
num_bytes++;
} else if (sc->state.transfer_word_size == 2) {
if (qcom_spi_hw_write_from_tx_buf(sc, 24, &reg))
num_bytes++;
if (qcom_spi_hw_write_from_tx_buf(sc, 16, &reg))
num_bytes++;
} else if (sc->state.transfer_word_size == 4) {
if (qcom_spi_hw_write_from_tx_buf(sc, 24, &reg))
num_bytes++;
if (qcom_spi_hw_write_from_tx_buf(sc, 16, &reg))
num_bytes++;
if (qcom_spi_hw_write_from_tx_buf(sc, 8, &reg))
num_bytes++;
if (qcom_spi_hw_write_from_tx_buf(sc, 0, &reg))
num_bytes++;
}
/*
* always shift out something in case we need phantom
* writes to finish things up whilst we read a reply
* payload.
*/
QCOM_SPI_WRITE_4(sc, QUP_OUTPUT_FIFO, reg);
QCOM_SPI_BARRIER_WRITE(sc);
}
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_TX_FIFO,
"%s: wrote %d bytes (%d fifo slots)\n",
__func__, num_bytes, sc->transfer.num_words);
return (0);
}
int
qcom_spi_hw_write_pio_block(struct qcom_spi_softc *sc)
{
/* Not yet implemented */
return (ENXIO);
}
/*
* Read data into the the RX buffer and increment the RX offset.
*
* Return true if the byte was saved into the RX buffer, else
* return false.
*/
static bool
qcom_spi_hw_read_into_rx_buf(struct qcom_spi_softc *sc, uint8_t val)
{
QCOM_SPI_ASSERT_LOCKED(sc);
if (sc->transfer.rx_buf == NULL)
return false;
/* Make sure we aren't overflowing the receive buffer */
if (sc->transfer.rx_offset < sc->transfer.rx_len) {
sc->transfer.rx_buf[sc->transfer.rx_offset] = val;
sc->transfer.rx_offset++;
return true;
}
return false;
}
/*
* Read "n_words" transfers, and push those bytes into the receive buffer.
* Make sure we have enough space, and make sure we don't overflow the
* read buffer size too!
*/
int
qcom_spi_hw_read_pio_fifo(struct qcom_spi_softc *sc)
{
uint32_t i;
uint32_t reg;
int num_bytes = 0;
QCOM_SPI_ASSERT_LOCKED(sc);
QCOM_SPI_WRITE_4(sc, QUP_OPERATIONAL, QUP_OP_IN_SERVICE_FLAG);
QCOM_SPI_BARRIER_WRITE(sc);
for (i = 0; i < sc->transfer.num_words; i++) {
/* Break if FIFO is empty */
QCOM_SPI_BARRIER_READ(sc);
reg = QCOM_SPI_READ_4(sc, QUP_OPERATIONAL);
if ((reg & QUP_OP_IN_FIFO_NOT_EMPTY) == 0) {
device_printf(sc->sc_dev, "%s: FIFO empty\n", __func__);
break;
}
/*
* Always read num_words up to FIFO being non-empty; that way
* if we have mis-matching TX/RX buffer sizes for some reason
* we will read the needed phantom bytes.
*/
reg = QCOM_SPI_READ_4(sc, QUP_INPUT_FIFO);
/*
* Unpack the receive buffer based on whether we are
* doing 1, 2, or 4 byte transfer words.
*/
if (sc->state.transfer_word_size == 1) {
if (qcom_spi_hw_read_into_rx_buf(sc, reg & 0xff))
num_bytes++;
} else if (sc->state.transfer_word_size == 2) {
if (qcom_spi_hw_read_into_rx_buf(sc, (reg >> 8) & 0xff))
num_bytes++;
if (qcom_spi_hw_read_into_rx_buf(sc, reg & 0xff))
num_bytes++;
} else if (sc->state.transfer_word_size == 4) {
if (qcom_spi_hw_read_into_rx_buf(sc, (reg >> 24) & 0xff))
num_bytes++;
if (qcom_spi_hw_read_into_rx_buf(sc, (reg >> 16) & 0xff))
num_bytes++;
if (qcom_spi_hw_read_into_rx_buf(sc, (reg >> 8) & 0xff))
num_bytes++;
if (qcom_spi_hw_read_into_rx_buf(sc, reg & 0xff))
num_bytes++;
}
}
QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_HW_TX_FIFO,
"%s: read %d bytes (%d transfer words)\n",
__func__, num_bytes, sc->transfer.num_words);
#if 0
/*
* This is a no-op for FIFO mode, it's only a thing for BLOCK
* transfers.
*/
QCOM_SPI_BARRIER_READ(sc);
reg = QCOM_SPI_READ_4(sc, QUP_OPERATIONAL);
if (reg & QUP_OP_MAX_INPUT_DONE_FLAG) {
device_printf(sc->sc_dev, "%s: read complete (DONE)\n" ,
__func__);
sc->intr.done = true;
}
#endif
#if 0
/*
* And see if we've finished the transfer and won't be getting
* any more. Then treat it as done as well.
*
* In FIFO only mode we don't get a completion interrupt;
* we get an interrupt when the FIFO has enough data present.
*/
if ((sc->state.transfer_mode == QUP_IO_M_MODE_FIFO)
&& (sc->transfer.rx_offset >= sc->transfer.rx_len)) {
device_printf(sc->sc_dev, "%s: read complete (rxlen)\n",
__func__);
sc->intr.done = true;
}
#endif
/*
* For FIFO transfers we get a /single/ result that complete
* the FIFO transfer. We won't get any subsequent transfers;
* we'll need to schedule a new FIFO transfer.
*/
sc->intr.done = true;
return (0);
}
int
qcom_spi_hw_read_pio_block(struct qcom_spi_softc *sc)
{
/* Not yet implemented */
return (ENXIO);
}
int
qcom_spi_hw_do_full_reset(struct qcom_spi_softc *sc)
{
QCOM_SPI_ASSERT_LOCKED(sc);
QCOM_SPI_WRITE_4(sc, QUP_SW_RESET, 1);
QCOM_SPI_BARRIER_WRITE(sc);
DELAY(100);
return (0);
}