mirror of
https://git.codelinaro.org/clo/qsdk/oss/boot/u-boot-2016.git
synced 2025-12-10 07:44:53 +01:00
Added read & write bam pipe entires in all the ipq specific dtsi. Also, updated the spi bam code with generic code changes to enable the multiple spi support on all ipq chipsets. Signed-off-by: Rajkumar Ayyasamy <arajkuma@codeaurora.org> Signed-off-by: Ram Kumar D <ramd@codeaurora.org> Change-Id: Ibcdb9d2a9ff7a25f3d296ecdb1aca403511e07d7
1046 lines
25 KiB
C
1046 lines
25 KiB
C
/*
|
|
* BLSP QUP SPI controller driver.
|
|
* Copyright (c) 2015, 2017 The Linux Foundation. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * 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.
|
|
* * Neither the name of The Linux Foundation nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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 <common.h>
|
|
#include <spi.h>
|
|
#include <malloc.h>
|
|
#include <asm/io.h>
|
|
#include <asm/errno.h>
|
|
#include <asm/arch-qca-common/bam.h>
|
|
#include "qca_qup_spi_bam.h"
|
|
#include <asm/arch-qca-common/gpio.h>
|
|
#include <fdtdec.h>
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
static unsigned int read_pipe[CONFIG_IPQ_MAX_BLSP_QUPS];
|
|
static unsigned int write_pipe[CONFIG_IPQ_MAX_BLSP_QUPS];
|
|
static unsigned char qup_pipe_initialized = 0;
|
|
|
|
static int check_bit_state(uint32_t reg_addr, int bit_num, int val,
|
|
int us_delay)
|
|
{
|
|
unsigned int count = TIMEOUT_CNT;
|
|
unsigned int bit_val = ((readl(reg_addr) >> bit_num) & 0x01);
|
|
|
|
while (bit_val != val) {
|
|
count--;
|
|
if (count == 0)
|
|
return -ETIMEDOUT;
|
|
udelay(us_delay);
|
|
bit_val = ((readl(reg_addr) >> bit_num) & 0x01);
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Check whether QUPn State is valid
|
|
*/
|
|
static int check_qup_state_valid(struct ipq_spi_slave *ds)
|
|
{
|
|
return check_bit_state(ds->regs->qup_state, QUP_STATE_VALID_BIT,
|
|
QUP_STATE_VALID, 1);
|
|
}
|
|
|
|
/*
|
|
* Configure QUPn Core state
|
|
*/
|
|
static int config_spi_state(struct ipq_spi_slave *ds, unsigned int state)
|
|
{
|
|
uint32_t val;
|
|
int ret;
|
|
|
|
ret = check_qup_state_valid(ds);
|
|
if (ret != SUCCESS)
|
|
return ret;
|
|
|
|
switch (state) {
|
|
case SPI_RUN_STATE:
|
|
/* Set the state to RUN */
|
|
val = ((readl(ds->regs->qup_state) & ~QUP_STATE_MASK)
|
|
| QUP_STATE_RUN_STATE);
|
|
writel(val, ds->regs->qup_state);
|
|
ret = check_qup_state_valid(ds);
|
|
if (ret != SUCCESS)
|
|
return ret;
|
|
ds->core_state = SPI_CORE_RUNNING;
|
|
break;
|
|
case SPI_RESET_STATE:
|
|
/* Set the state to RESET */
|
|
val = ((readl(ds->regs->qup_state) & ~QUP_STATE_MASK)
|
|
| QUP_STATE_RESET_STATE);
|
|
writel(val, ds->regs->qup_state);
|
|
ret = check_qup_state_valid(ds);
|
|
if (ret != SUCCESS)
|
|
return ret;
|
|
ds->core_state = SPI_CORE_RESET;
|
|
break;
|
|
default:
|
|
printf("err: unsupported QUP SPI state : %d\n", state);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Set QUPn SPI Mode
|
|
*/
|
|
static void spi_set_mode(struct ipq_spi_slave *ds, unsigned int mode)
|
|
{
|
|
unsigned int clk_idle_state;
|
|
unsigned int input_first_mode;
|
|
uint32_t val;
|
|
|
|
switch (mode) {
|
|
case SPI_MODE0:
|
|
clk_idle_state = 0;
|
|
input_first_mode = SPI_INPUT_FIRST_MODE;
|
|
break;
|
|
case SPI_MODE1:
|
|
clk_idle_state = 0;
|
|
input_first_mode = 0;
|
|
break;
|
|
case SPI_MODE2:
|
|
clk_idle_state = 1;
|
|
input_first_mode = SPI_INPUT_FIRST_MODE;
|
|
break;
|
|
case SPI_MODE3:
|
|
clk_idle_state = 1;
|
|
input_first_mode = 0;
|
|
break;
|
|
default:
|
|
printf("err : unsupported spi mode : %d\n", mode);
|
|
return;
|
|
}
|
|
|
|
val = readl(ds->regs->spi_config);
|
|
val |= input_first_mode;
|
|
writel(val, ds->regs->spi_config);
|
|
|
|
val = readl(ds->regs->io_control);
|
|
if (clk_idle_state)
|
|
val |= SPI_IO_CONTROL_CLOCK_IDLE_HIGH;
|
|
else
|
|
val &= ~SPI_IO_CONTROL_CLOCK_IDLE_HIGH;
|
|
|
|
writel(val, ds->regs->io_control);
|
|
}
|
|
|
|
/*
|
|
* Reset entire QUP and all mini cores
|
|
*/
|
|
static void spi_reset(struct ipq_spi_slave *ds)
|
|
{
|
|
writel(0x1, ds->regs->qup_sw_reset);
|
|
udelay(5);
|
|
}
|
|
|
|
void spi_init()
|
|
{
|
|
/* do nothing */
|
|
}
|
|
|
|
static void qup_pipe_init(void)
|
|
{
|
|
char rd_pipe_name[10], wr_pipe_name[10];
|
|
int node,i;
|
|
|
|
qup_pipe_initialized = 1;
|
|
node = fdt_path_offset(gd->fdt_blob, "/spi");
|
|
if (node >= 0) {
|
|
for(i = 0; i < CONFIG_IPQ_MAX_BLSP_QUPS; i++) {
|
|
|
|
snprintf(rd_pipe_name, sizeof(rd_pipe_name),
|
|
"rd_pipe_%01d", i);
|
|
|
|
snprintf(wr_pipe_name, sizeof(wr_pipe_name),
|
|
"wr_pipe_%01d", i);
|
|
|
|
read_pipe[i] = fdtdec_get_uint(gd->fdt_blob,
|
|
node, rd_pipe_name, 0);
|
|
write_pipe[i] = fdtdec_get_uint(gd->fdt_blob,
|
|
node, wr_pipe_name, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Function to assert and De-assert chip select
|
|
*/
|
|
static void CS_change(int port_num, int cs_num, int enable)
|
|
{
|
|
unsigned int cs_gpio = 0;
|
|
uint32_t addr = 0;
|
|
uint32_t val = 0;
|
|
char spi_node_path[32];
|
|
int spi_cs_node = 0, spi_cs_gpio_node = 0;
|
|
|
|
snprintf(spi_node_path, sizeof(spi_node_path),
|
|
"/spi/spi%d/cs%d", port_num, cs_num);
|
|
|
|
spi_cs_node = fdt_path_offset(gd->fdt_blob, spi_node_path);
|
|
spi_cs_gpio_node = fdt_first_subnode(gd->fdt_blob, spi_cs_node);
|
|
|
|
cs_gpio = fdtdec_get_uint(gd->fdt_blob, spi_cs_gpio_node, "gpio", 0);
|
|
|
|
addr = GPIO_IN_OUT_ADDR(cs_gpio);
|
|
val = readl(addr);
|
|
|
|
val &= (~(GPIO_OUT));
|
|
if (!enable)
|
|
val |= (GPIO_OUT);
|
|
writel(val, addr);
|
|
}
|
|
|
|
/*
|
|
* BLSP TLMM configuration
|
|
*/
|
|
int blsp_pin_config(unsigned int port_num, int cs_num)
|
|
{
|
|
char spi_node_path[32];
|
|
int spi_node = 0;
|
|
|
|
snprintf(spi_node_path, sizeof(spi_node_path),
|
|
"/spi/spi%d/cs%d", port_num, cs_num);
|
|
|
|
spi_node = fdt_path_offset(gd->fdt_blob, spi_node_path);
|
|
if (spi_node >= 0) {
|
|
qca_gpio_init(spi_node);
|
|
} else {
|
|
printf("SPI : Node not found, skipping initialization\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cs_is_valid(unsigned int port_num, int cs_num)
|
|
{
|
|
char spi_node_path[32];
|
|
int spi_node = 0;
|
|
|
|
snprintf(spi_node_path, sizeof(spi_node_path),
|
|
"/spi/spi%d/cs%d", port_num, cs_num);
|
|
|
|
spi_node = fdt_path_offset(gd->fdt_blob, spi_node_path);
|
|
if (spi_node >= 0)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
int qup_bam_init(struct ipq_spi_slave *ds)
|
|
{
|
|
uint8_t read_pipe_grp = QUP0_DATA_PRODUCER_PIPE_GRP;
|
|
uint8_t write_pipe_grp = QUP0_DATA_CONSUMER_PIPE_GRP;
|
|
int bam_ret;
|
|
|
|
if (!qup_pipe_initialized)
|
|
qup_pipe_init();
|
|
|
|
/* Pipe numbers based on the QUP index */
|
|
if (ds->slave.bus == BLSP0_SPI) {
|
|
read_pipe_grp = QUP0_DATA_PRODUCER_PIPE_GRP;
|
|
write_pipe_grp = QUP0_DATA_CONSUMER_PIPE_GRP;
|
|
} else if (ds->slave.bus == BLSP1_SPI) {
|
|
read_pipe_grp = QUP1_DATA_PRODUCER_PIPE_GRP;
|
|
write_pipe_grp = QUP1_DATA_CONSUMER_PIPE_GRP;
|
|
}
|
|
|
|
bam.base = BLSP0_BAM_BASE;
|
|
/* Set Read Pipe Params */
|
|
bam.pipe[DATA_PRODUCER_PIPE_INDEX].pipe_num = read_pipe[ds->slave.bus];
|
|
bam.pipe[DATA_PRODUCER_PIPE_INDEX].trans_type = BAM2SYS;
|
|
bam.pipe[DATA_PRODUCER_PIPE_INDEX].fifo.size = QUP_BAM_DATA_FIFO_SIZE;
|
|
bam.pipe[DATA_PRODUCER_PIPE_INDEX].fifo.head = qup_spi_data_desc_fifo;
|
|
bam.pipe[DATA_PRODUCER_PIPE_INDEX].lock_grp = read_pipe_grp;
|
|
|
|
/* Set Write pipe params. */
|
|
bam.pipe[DATA_CONSUMER_PIPE_INDEX].pipe_num = write_pipe[ds->slave.bus];
|
|
bam.pipe[DATA_CONSUMER_PIPE_INDEX].trans_type = SYS2BAM;
|
|
bam.pipe[DATA_CONSUMER_PIPE_INDEX].fifo.size = QUP_BAM_DATA_FIFO_SIZE;
|
|
bam.pipe[DATA_CONSUMER_PIPE_INDEX].fifo.head = qup_spi_data_desc_fifo;
|
|
bam.pipe[DATA_CONSUMER_PIPE_INDEX].lock_grp = write_pipe_grp;
|
|
|
|
/* Programs the threshold for BAM transfer
|
|
* When this threshold is reached, BAM signals the peripheral via the
|
|
* pipe_bytes_available interface.
|
|
* The peripheral is signalled with this notification in the following
|
|
* cases:
|
|
* a. It has accumulated all the descriptors.
|
|
* b. It has accumulated more than threshold bytes.
|
|
* c. It has reached EOT (End Of Transfer).
|
|
* Note: this value needs to be set by the h/w folks and is specific for
|
|
* each peripheral.
|
|
*/
|
|
bam.threshold = QUP_SPI_BAM_THRESHOLD;
|
|
/* Set the EE. */
|
|
bam.ee = QUP_SPI_EE;
|
|
/* Set the max desc length for this BAM. */
|
|
bam.max_desc_len = QUP_SPI_MAX_DESC_LEN;
|
|
/* BAM Init. */
|
|
bam_init(&bam);
|
|
/* Initialize BAM QPIC read pipe */
|
|
bam_sys_pipe_init(&bam, DATA_PRODUCER_PIPE_INDEX);
|
|
/* Init read fifo */
|
|
bam_ret = bam_pipe_fifo_init(&bam, DATA_PRODUCER_PIPE_INDEX);
|
|
if (bam_ret) {
|
|
printf("QUP: SPI: BAM Read FIFO init error\n");
|
|
bam_ret = FAILURE;
|
|
goto qup_spi_bam_init_error;
|
|
}
|
|
|
|
/* Initialize BAM QPIC write pipe */
|
|
bam_sys_pipe_init(&bam, DATA_CONSUMER_PIPE_INDEX);
|
|
|
|
/* Init write fifo. Use the same fifo as read fifo. */
|
|
bam_ret = bam_pipe_fifo_init(&bam, DATA_CONSUMER_PIPE_INDEX);
|
|
if (bam_ret) {
|
|
printf("QUP: SPI: BAM Write FIFO init error\n");
|
|
bam_ret = FAILURE;
|
|
goto qup_spi_bam_init_error;
|
|
}
|
|
qup_spi_bam_init_error:
|
|
return bam_ret;
|
|
}
|
|
|
|
struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs,
|
|
unsigned int max_hz, unsigned int mode)
|
|
{
|
|
struct ipq_spi_slave *ds;
|
|
|
|
/*
|
|
* spi gpios are not initialised for DK07-C2. If probed, the
|
|
* controller will indefinitely wait for response from slave.
|
|
* Hence, return NULL.
|
|
*/
|
|
ds = malloc(sizeof(struct ipq_spi_slave));
|
|
if (!ds) {
|
|
printf("SPI error: malloc of SPI structure failed\n");
|
|
return NULL;
|
|
}
|
|
|
|
memset(ds, 0, sizeof(struct ipq_spi_slave));
|
|
/*
|
|
* QCA BLSP supports SPI Flash
|
|
* on different BLSP 0 to CONFIG_IPQ_MAX_BLSP_QUPS-1
|
|
* with different number of chip selects (CS, channels):
|
|
*/
|
|
if (bus >= CONFIG_IPQ_MAX_BLSP_QUPS){
|
|
printf("SPI error: unsupported bus %d "
|
|
"Supported busses 0 to %d\n", bus, CONFIG_IPQ_MAX_BLSP_QUPS-1);
|
|
goto err;
|
|
}
|
|
ds->slave.bus = bus;
|
|
ds->slave.cs = cs;
|
|
|
|
BLSP_SPI_REGISTERS(spi_reg[bus]);
|
|
|
|
ds->regs = &spi_reg[bus];
|
|
|
|
/* TODO For different clock frequency */
|
|
if (max_hz > MSM_QUP_MAX_FREQ) {
|
|
printf("SPI error: unsupported frequency %d Hz "
|
|
"Max frequency is %d Hz\n", max_hz, MSM_QUP_MAX_FREQ);
|
|
goto err;
|
|
}
|
|
ds->freq = max_hz;
|
|
|
|
if (mode > SPI_MODE3) {
|
|
printf("SPI error: unsupported SPI mode %d\n", mode);
|
|
goto err;
|
|
}
|
|
ds->mode = mode;
|
|
|
|
/* DMA mode */
|
|
ds->use_dma = CONFIG_QUP_SPI_USE_DMA;
|
|
|
|
if (ds->slave.cs >= 1 &&
|
|
cs_is_valid(ds->slave.bus, ds->slave.cs)) {
|
|
/* GPIO Configuration for SPI NAND */
|
|
blsp_pin_config(ds->slave.bus, ds->slave.cs);
|
|
CS_change(ds->slave.bus, ds->slave.cs, CS_DEASSERT);
|
|
}
|
|
#if !defined(CONFIG_SYS_DCACHE_OFF)
|
|
flush_cache((unsigned long)ds,
|
|
(unsigned long)sizeof(struct ipq_spi_slave));
|
|
#endif
|
|
return &ds->slave;
|
|
err:
|
|
free(ds);
|
|
return NULL;
|
|
}
|
|
|
|
void spi_free_slave(struct spi_slave *slave)
|
|
{
|
|
struct ipq_spi_slave *ds = to_ipq_spi(slave);
|
|
|
|
if (ds != NULL)
|
|
free(ds);
|
|
}
|
|
|
|
/*
|
|
* BLSP QUPn SPI Hardware Initialisation
|
|
*/
|
|
static int spi_hw_init(struct ipq_spi_slave *ds)
|
|
{
|
|
int ret;
|
|
|
|
ds->initialized = 0;
|
|
|
|
/* QUPn module configuration */
|
|
spi_reset(ds);
|
|
|
|
/* Set the QUPn state */
|
|
ret = config_spi_state(ds, SPI_RESET_STATE);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Initialize BAM */
|
|
if(ds->use_dma) {
|
|
ret = qup_bam_init(ds);
|
|
if (ret != SUCCESS)
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Configure Mini core to SPI core with Input Output enabled,
|
|
* SPI master, N = 8 bits
|
|
*/
|
|
clrsetbits_le32(ds->regs->qup_config, (QUP_CONFIG_MINI_CORE_MSK |
|
|
QUP_CONF_INPUT_MSK |
|
|
QUP_CONF_OUTPUT_MSK |
|
|
SPI_BIT_WORD_MSK),
|
|
(QUP_CONFIG_MINI_CORE_SPI |
|
|
QUP_CONF_INPUT_ENA |
|
|
QUP_CONF_OUTPUT_ENA |
|
|
SPI_8_BIT_WORD));
|
|
|
|
/*
|
|
* Configure Input first SPI protocol,
|
|
* SPI master mode and no loopback
|
|
*/
|
|
clrsetbits_le32(ds->regs->spi_config, (LOOP_BACK_MSK |
|
|
SLAVE_OPERATION_MSK),
|
|
(NO_LOOP_BACK |
|
|
SLAVE_OPERATION));
|
|
|
|
/*
|
|
* Configure SPI IO Control Register
|
|
* CLK_ALWAYS_ON = 0
|
|
* MX_CS_MODE = 0
|
|
* NO_TRI_STATE = 1
|
|
*/
|
|
writel((CLK_ALWAYS_ON | NO_TRI_STATE),
|
|
ds->regs->io_control);
|
|
|
|
/*
|
|
* Configure SPI IO Modes.
|
|
* OUTPUT_BIT_SHIFT_EN = 1
|
|
* INPUT_MODE = BAM Mode
|
|
* OUTPUT MODE = BAM Mode
|
|
*/
|
|
|
|
if (ds->use_dma)
|
|
clrsetbits_le32(ds->regs->qup_io_modes, (OUTPUT_BIT_SHIFT_MSK |
|
|
INPUT_BLOCK_MODE_MSK |
|
|
OUTPUT_BLOCK_MODE_MSK |
|
|
PACK_EN_MSK |
|
|
UNPACK_EN_MSK),
|
|
(OUTPUT_BIT_SHIFT_EN |
|
|
INPUT_BAM_MODE |
|
|
OUTPUT_BAM_MODE | PACK_EN |
|
|
UNPACK_EN));
|
|
else
|
|
/*
|
|
* Configure SPI IO Modes.
|
|
* OUTPUT_BIT_SHIFT_EN = 1
|
|
* INPUT_MODE = Block Mode
|
|
* OUTPUT MODE = Block Mode
|
|
*/
|
|
|
|
clrsetbits_le32(ds->regs->qup_io_modes, (OUTPUT_BIT_SHIFT_MSK |
|
|
INPUT_BLOCK_MODE_MSK |
|
|
OUTPUT_BLOCK_MODE_MSK),
|
|
(OUTPUT_BIT_SHIFT_EN |
|
|
INPUT_BLOCK_MODE |
|
|
OUTPUT_BLOCK_MODE));
|
|
|
|
spi_set_mode(ds, ds->mode);
|
|
|
|
/* Disable Error mask */
|
|
writel(0, ds->regs->error_flags_en);
|
|
writel(0, ds->regs->qup_error_flags_en);
|
|
writel(0, ds->regs->qup_deassert_wait);
|
|
|
|
if (ds->use_dma)
|
|
clrsetbits_le32(ds->regs->qup_op_mask, (OUTPUT_SERVICE_MSK |
|
|
INPUT_SERVICE_MSK),
|
|
(OUTPUT_SERVICE_DIS |
|
|
INPUT_SERVICE_DIS));
|
|
ds->initialized = 1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int spi_claim_bus(struct spi_slave *slave)
|
|
{
|
|
struct ipq_spi_slave *ds = to_ipq_spi(slave);
|
|
unsigned int ret;
|
|
|
|
ret = spi_hw_init(ds);
|
|
if (ret)
|
|
return -EIO;
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
void spi_release_bus(struct spi_slave *slave)
|
|
{
|
|
struct ipq_spi_slave *ds = to_ipq_spi(slave);
|
|
|
|
/* Reset the SPI hardware */
|
|
spi_reset(ds);
|
|
ds->initialized = 0;
|
|
}
|
|
|
|
static void write_force_cs(struct spi_slave *slave, int assert)
|
|
{
|
|
struct ipq_spi_slave *ds = to_ipq_spi(slave);
|
|
|
|
if (assert)
|
|
clrsetbits_le32(ds->regs->io_control,
|
|
FORCE_CS_MSK, FORCE_CS_EN);
|
|
else
|
|
clrsetbits_le32(ds->regs->io_control,
|
|
FORCE_CS_MSK, FORCE_CS_DIS);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/*
|
|
* Function to write data to OUTPUT FIFO
|
|
*/
|
|
static void spi_write_byte(struct ipq_spi_slave *ds, unsigned char data)
|
|
{
|
|
/* Wait for space in the FIFO */
|
|
while ((readl(ds->regs->qup_operational) & QUP_OUTPUT_FIFO_FULL))
|
|
udelay(1);
|
|
|
|
/* Write the byte of data */
|
|
writel(data, ds->regs->qup_output_fifo);
|
|
}
|
|
|
|
/*
|
|
* Function to read data from Input FIFO
|
|
*/
|
|
static unsigned char spi_read_byte(struct ipq_spi_slave *ds)
|
|
{
|
|
/* Wait for Data in FIFO */
|
|
while (!(readl(ds->regs->qup_operational) &
|
|
QUP_DATA_AVAILABLE_FOR_READ)) {
|
|
udelay(1);
|
|
}
|
|
|
|
/* Read a byte of data */
|
|
return readl(ds->regs->qup_input_fifo) & 0xff;
|
|
}
|
|
|
|
/*
|
|
* Function to check wheather Input or Output FIFO
|
|
* has data to be serviced
|
|
*/
|
|
static int check_fifo_status(uint32_t reg_addr)
|
|
{
|
|
unsigned int count = TIMEOUT_CNT;
|
|
unsigned int status_flag;
|
|
unsigned int val;
|
|
|
|
do {
|
|
val = readl(reg_addr);
|
|
count--;
|
|
if (count == 0)
|
|
return -ETIMEDOUT;
|
|
status_flag = ((val & OUTPUT_SERVICE_FLAG) | (val & INPUT_SERVICE_FLAG));
|
|
} while (!status_flag);
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Function to configure Input and Output enable/disable
|
|
*/
|
|
static void enable_io_config(struct ipq_spi_slave *ds,
|
|
uint32_t write_cnt, uint32_t read_cnt)
|
|
{
|
|
if (write_cnt) {
|
|
clrsetbits_le32(ds->regs->qup_config,
|
|
QUP_CONF_OUTPUT_MSK, QUP_CONF_OUTPUT_ENA);
|
|
} else {
|
|
clrsetbits_le32(ds->regs->qup_config,
|
|
QUP_CONF_OUTPUT_MSK, QUP_CONF_NO_OUTPUT);
|
|
}
|
|
|
|
if (read_cnt) {
|
|
clrsetbits_le32(ds->regs->qup_config,
|
|
QUP_CONF_INPUT_MSK, QUP_CONF_INPUT_ENA);
|
|
} else {
|
|
clrsetbits_le32(ds->regs->qup_config,
|
|
QUP_CONF_INPUT_MSK, QUP_CONF_NO_INPUT);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
blsp_spi_wait_for_data(uint32_t pipe_num)
|
|
{
|
|
/* Wait for the descriptors to be processed */
|
|
bam_wait_for_interrupt(&bam, pipe_num, P_PRCSD_DESC_EN_MASK);
|
|
|
|
/* Read offset update for the circular FIFO */
|
|
bam_read_offset_update(&bam, pipe_num);
|
|
}
|
|
|
|
|
|
static int blsp_spi_bam_begin_xfer(struct ipq_spi_slave *ds, const u8 *buffer,
|
|
unsigned int bytes, unsigned int type)
|
|
{
|
|
u32 tx_bytes_to_send = 0, rx_bytes_to_recv = 0;
|
|
u32 n_words_xfr;
|
|
u32 ret = 0;
|
|
u32 prod_desc_cnt = SPI_BAM_MAX_DESC_NUM - 1;
|
|
u32 cons_desc_cnt = SPI_BAM_MAX_DESC_NUM - 1;
|
|
u32 tx_bytes_sent = 0;
|
|
u32 rx_bytes_rcvd = 0;
|
|
int state_config;
|
|
u32 data_xfer_size;
|
|
int cons_flag = 0, prod_flag = 0;
|
|
int num_desc = 0;
|
|
u32 rem_bytes = 0;
|
|
|
|
rem_bytes = bytes;
|
|
|
|
#if !defined(CONFIG_SYS_DCACHE_OFF)
|
|
flush_cache((unsigned long)buffer, (unsigned long)bytes);
|
|
#endif
|
|
while (rem_bytes) {
|
|
tx_bytes_to_send = min_t(u32, bytes,
|
|
SPI_MAX_TRFR_BTWN_RESETS);
|
|
|
|
rx_bytes_to_recv = min_t(u32, bytes,
|
|
SPI_MAX_TRFR_BTWN_RESETS);
|
|
|
|
if (bytes > SPI_MAX_TRFR_BTWN_RESETS)
|
|
rem_bytes = bytes - SPI_MAX_TRFR_BTWN_RESETS;
|
|
else
|
|
rem_bytes = 0;
|
|
|
|
writel(0, ds->regs->qup_mx_output_count);
|
|
if (type == READ) {
|
|
n_words_xfr = rx_bytes_to_recv;
|
|
writel(n_words_xfr, ds->regs->qup_mx_input_count);
|
|
}
|
|
|
|
state_config = config_spi_state(ds, SPI_RUN_STATE);
|
|
if (state_config)
|
|
return state_config;
|
|
|
|
|
|
if (type == WRITE) {
|
|
if (cons_desc_cnt > 0) {
|
|
data_xfer_size = tx_bytes_to_send;
|
|
|
|
if (rem_bytes == 0 || cons_desc_cnt == 1)
|
|
cons_flag = BAM_DESC_EOT_FLAG |
|
|
BAM_DESC_NWD_FLAG;
|
|
cons_flag |= BAM_DESC_INT_FLAG;
|
|
bam_add_one_desc(&bam, DATA_CONSUMER_PIPE_INDEX,
|
|
(unsigned char*)(buffer),
|
|
data_xfer_size, cons_flag);
|
|
num_desc++;
|
|
bam_sys_gen_event(&bam, DATA_CONSUMER_PIPE_INDEX,
|
|
num_desc);
|
|
blsp_spi_wait_for_data(DATA_CONSUMER_PIPE_INDEX);
|
|
num_desc = 0;
|
|
tx_bytes_sent += data_xfer_size;
|
|
buffer = buffer + data_xfer_size;
|
|
bytes = rem_bytes;
|
|
cons_desc_cnt--;
|
|
if (cons_desc_cnt == 0)
|
|
cons_desc_cnt = SPI_BAM_MAX_DESC_NUM - 1;
|
|
|
|
}
|
|
} else if (type == READ) {
|
|
if (prod_desc_cnt > 0) {
|
|
data_xfer_size = rx_bytes_to_recv;
|
|
if (rem_bytes == 0 || prod_desc_cnt == 1)
|
|
prod_flag = (BAM_DESC_EOT_FLAG |
|
|
BAM_DESC_NWD_FLAG);
|
|
prod_flag |= BAM_DESC_INT_FLAG;
|
|
bam_add_one_desc(&bam, DATA_PRODUCER_PIPE_INDEX,
|
|
(unsigned char*)(buffer), data_xfer_size, prod_flag);
|
|
num_desc++;
|
|
bam_sys_gen_event(&bam, DATA_PRODUCER_PIPE_INDEX,
|
|
num_desc);
|
|
blsp_spi_wait_for_data(DATA_PRODUCER_PIPE_INDEX);
|
|
#if !defined(CONFIG_SYS_DCACHE_OFF)
|
|
flush_cache((unsigned long)buffer,
|
|
(unsigned long)bytes);
|
|
#endif
|
|
num_desc = 0;
|
|
rx_bytes_rcvd += data_xfer_size;
|
|
buffer = buffer + data_xfer_size;
|
|
bytes = rem_bytes;
|
|
prod_desc_cnt--;
|
|
if (prod_desc_cnt == 0)
|
|
prod_desc_cnt = SPI_BAM_MAX_DESC_NUM - 1;
|
|
}
|
|
}
|
|
state_config = config_spi_state(ds, SPI_RESET_STATE);
|
|
if (state_config)
|
|
return state_config;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Function to read bytes number of data from the Input FIFO
|
|
*/
|
|
static int __blsp_spi_read(struct ipq_spi_slave *ds, u8 *data_buffer,
|
|
unsigned int bytes)
|
|
{
|
|
uint32_t val;
|
|
unsigned int i;
|
|
unsigned int read_bytes = bytes;
|
|
unsigned int fifo_count;
|
|
int ret = SUCCESS;
|
|
int state_config;
|
|
|
|
|
|
/* Configure no of bytes to read */
|
|
state_config = config_spi_state(ds, SPI_RESET_STATE);
|
|
if (state_config)
|
|
return state_config;
|
|
|
|
/* Configure input and output enable */
|
|
enable_io_config(ds, 0, read_bytes);
|
|
|
|
if (!(ds->use_dma))
|
|
writel(bytes, ds->regs->qup_mx_input_count);
|
|
|
|
if (ds->use_dma && read_bytes)
|
|
blsp_spi_bam_begin_xfer(ds, data_buffer,
|
|
bytes, READ);
|
|
else {
|
|
state_config = config_spi_state(ds, SPI_RUN_STATE);
|
|
if (state_config)
|
|
return state_config;
|
|
|
|
while (read_bytes) {
|
|
ret = check_fifo_status(ds->regs->qup_operational);
|
|
if (ret != SUCCESS)
|
|
goto out;
|
|
|
|
val = readl(ds->regs->qup_operational);
|
|
if (val & INPUT_SERVICE_FLAG) {
|
|
/*
|
|
* acknowledge to hw that software will
|
|
* read input data
|
|
*/
|
|
val &= INPUT_SERVICE_FLAG;
|
|
writel(val, ds->regs->qup_operational);
|
|
|
|
fifo_count = ((read_bytes > SPI_INPUT_BLOCK_SIZE) ?
|
|
SPI_INPUT_BLOCK_SIZE : read_bytes);
|
|
|
|
for (i = 0; i < fifo_count; i++) {
|
|
*data_buffer = spi_read_byte(ds);
|
|
data_buffer++;
|
|
read_bytes--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
/*
|
|
* Put the SPI Core back in the Reset State
|
|
* to end the transfer
|
|
*/
|
|
(void)config_spi_state(ds, SPI_RESET_STATE);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int blsp_spi_read(struct ipq_spi_slave *ds, u8 *data_buffer,
|
|
unsigned int bytes)
|
|
{
|
|
int length, ret;
|
|
|
|
if (!(ds->use_dma)) {
|
|
while (bytes) {
|
|
length = (bytes < MAX_COUNT_SIZE) ? bytes : MAX_COUNT_SIZE;
|
|
|
|
ret = __blsp_spi_read(ds, data_buffer, length);
|
|
if (ret != SUCCESS)
|
|
return ret;
|
|
|
|
data_buffer += length;
|
|
bytes -= length;
|
|
}
|
|
} else {
|
|
ret = __blsp_spi_read(ds, data_buffer, bytes);
|
|
if (ret != SUCCESS)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function to write data to the Output FIFO
|
|
*/
|
|
static int __blsp_spi_write(struct ipq_spi_slave *ds, const u8 *cmd_buffer,
|
|
unsigned int bytes)
|
|
{
|
|
uint32_t val;
|
|
unsigned int i;
|
|
unsigned int write_len = bytes;
|
|
unsigned int read_len = bytes;
|
|
unsigned int fifo_count;
|
|
int ret = SUCCESS;
|
|
int state_config;
|
|
|
|
state_config = config_spi_state(ds, SPI_RESET_STATE);
|
|
if (state_config)
|
|
return state_config;
|
|
|
|
if (ds->use_dma) {
|
|
/* No of bytes to be written in Output FIFO */
|
|
writel(0, ds->regs->qup_mx_read_count);
|
|
writel(0, ds->regs->qup_mx_write_count);
|
|
writel(0, ds->regs->qup_mx_output_count);
|
|
writel(0, ds->regs->qup_mx_input_count);
|
|
}
|
|
else {
|
|
writel(bytes, ds->regs->qup_mx_output_count);
|
|
writel(bytes, ds->regs->qup_mx_input_count);
|
|
state_config = config_spi_state(ds, SPI_RUN_STATE);
|
|
if (state_config)
|
|
return state_config;
|
|
}
|
|
|
|
/* Configure input and output enable */
|
|
if (!(ds->use_dma)) {
|
|
enable_io_config(ds, write_len, read_len);
|
|
} else {
|
|
enable_io_config(ds, write_len, 0);
|
|
}
|
|
|
|
if (ds->use_dma && write_len)
|
|
blsp_spi_bam_begin_xfer(ds, cmd_buffer,
|
|
bytes, WRITE);
|
|
|
|
else {
|
|
/*
|
|
* read_len considered to ensure that we read the dummy data for the
|
|
* write we performed. This is needed to ensure with WR-RD transaction
|
|
* to get the actual data on the subsequent read cycle that happens
|
|
*/
|
|
while (write_len || read_len) {
|
|
ret = check_fifo_status(ds->regs->qup_operational);
|
|
if (ret != SUCCESS)
|
|
goto out;
|
|
|
|
val = readl(ds->regs->qup_operational);
|
|
if (val & OUTPUT_SERVICE_FLAG) {
|
|
/*
|
|
* acknowledge to hw that software will write
|
|
* expected output data
|
|
*/
|
|
val &= OUTPUT_SERVICE_FLAG;
|
|
writel(val, ds->regs->qup_operational);
|
|
|
|
if (write_len > SPI_OUTPUT_BLOCK_SIZE)
|
|
fifo_count = SPI_OUTPUT_BLOCK_SIZE;
|
|
else
|
|
fifo_count = write_len;
|
|
|
|
for (i = 0; i < fifo_count; i++) {
|
|
/* Write actual data to output FIFO */
|
|
spi_write_byte(ds, *cmd_buffer);
|
|
cmd_buffer++;
|
|
write_len--;
|
|
}
|
|
}
|
|
if (val & INPUT_SERVICE_FLAG) {
|
|
/*
|
|
* acknowledge to hw that software
|
|
* will read input data
|
|
*/
|
|
val &= INPUT_SERVICE_FLAG;
|
|
writel(val, ds->regs->qup_operational);
|
|
|
|
if (read_len > SPI_INPUT_BLOCK_SIZE)
|
|
fifo_count = SPI_INPUT_BLOCK_SIZE;
|
|
else
|
|
fifo_count = read_len;
|
|
|
|
for (i = 0; i < fifo_count; i++) {
|
|
/* Read dummy data for the data written */
|
|
(void)spi_read_byte(ds);
|
|
|
|
/* Decrement the write count after reading the
|
|
* dummy data from the device. This is to make
|
|
* sure we read dummy data before we write the
|
|
* data to fifo
|
|
*/
|
|
read_len--;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
out:
|
|
/*
|
|
* Put the SPI Core back in the Reset State
|
|
* to end the transfer
|
|
*/
|
|
(void)config_spi_state(ds, SPI_RESET_STATE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int blsp_spi_write(struct ipq_spi_slave *ds, const u8 *cmd_buffer,
|
|
unsigned int bytes)
|
|
{
|
|
int length, ret;
|
|
|
|
if (!(ds->use_dma)) {
|
|
while (bytes) {
|
|
length = (bytes < MAX_COUNT_SIZE) ? bytes : MAX_COUNT_SIZE;
|
|
|
|
ret = __blsp_spi_write(ds, cmd_buffer, length);
|
|
if (ret != SUCCESS)
|
|
return ret;
|
|
|
|
cmd_buffer += length;
|
|
bytes -= length;
|
|
}
|
|
} else {
|
|
ret = __blsp_spi_write(ds, cmd_buffer, bytes);
|
|
if (ret != SUCCESS)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
/*
|
|
* This function is invoked with either tx_buf or rx_buf.
|
|
* Calling this function with both null does a chip select change.
|
|
*/
|
|
int spi_xfer(struct spi_slave *slave, unsigned int bitlen,
|
|
const void *dout, void *din, unsigned long flags)
|
|
{
|
|
struct ipq_spi_slave *ds = to_ipq_spi(slave);
|
|
unsigned int len;
|
|
const u8 *txp = dout;
|
|
u8 *rxp = din;
|
|
int ret = SUCCESS;
|
|
|
|
if (bitlen & SPI_BITLEN_MSK) {
|
|
printf("err : Invalid bit length");
|
|
return -EINVAL;
|
|
}
|
|
|
|
len = bitlen >> 3;
|
|
|
|
if (flags & SPI_XFER_BEGIN) {
|
|
if (!(ds->use_dma)) {
|
|
ret = spi_hw_init(ds);
|
|
if (ret != SUCCESS)
|
|
return ret;
|
|
}
|
|
|
|
if (ds->slave.cs == 1 &&
|
|
cs_is_valid(ds->slave.bus, ds->slave.cs)) {
|
|
/* SPI NAND CS Settings */
|
|
setbits_le32(ds->regs->io_control, CS_POLARITY_MASK);
|
|
CS_change(ds->slave.bus, ds->slave.cs, CS_ASSERT);
|
|
} else {
|
|
write_force_cs(slave, 1);
|
|
}
|
|
}
|
|
|
|
if (dout != NULL) {
|
|
ret = blsp_spi_write(ds, txp, len);
|
|
if (ret != SUCCESS)
|
|
return ret;
|
|
}
|
|
|
|
if (din != NULL) {
|
|
ret = blsp_spi_read(ds, rxp, len);
|
|
if (ret != SUCCESS)
|
|
return ret;
|
|
}
|
|
|
|
if (flags & SPI_XFER_END) {
|
|
if (ds->slave.cs == 1 &&
|
|
cs_is_valid(ds->slave.bus, ds->slave.cs)) {
|
|
/* SPI NAND CS Settings */
|
|
clrbits_le32(ds->regs->io_control, CS_POLARITY_MASK);
|
|
CS_change(ds->slave.bus, ds->slave.cs, CS_DEASSERT);
|
|
} else {
|
|
write_force_cs(slave, 0);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int spi_cs_is_valid(unsigned int bus, unsigned int cs)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
void spi_cs_activate(struct spi_slave *slave)
|
|
{
|
|
|
|
}
|
|
|
|
void spi_cs_deactivate(struct spi_slave *slave)
|
|
{
|
|
|
|
}
|