806 lines
18 KiB
C
806 lines
18 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
|
|
#include <device/mmio.h>
|
|
#include <console/console.h>
|
|
#include <delay.h>
|
|
#include <gpio.h>
|
|
#include <soc/iomap.h>
|
|
#include <soc/spi.h>
|
|
#include <types.h>
|
|
|
|
#define SUCCESS 0
|
|
|
|
#define DUMMY_DATA_VAL 0
|
|
#define TIMEOUT_CNT 100
|
|
#define CS_ASSERT 1
|
|
#define CS_DEASSERT 0
|
|
#define NUM_PORTS 3
|
|
#define NUM_GSBI_PINS 3
|
|
#define TLMM_ARGS 6
|
|
#define NUM_CS 4
|
|
#define GSBI_PIN_IDX 0
|
|
#define FUNC_SEL_IDX 1
|
|
#define GPIO_DIR_IDX 2
|
|
#define PULL_CONF_IDX 3
|
|
#define DRV_STR_IDX 4
|
|
#define GPIO_EN_IDX 5
|
|
|
|
/* Arbitrarily assigned error code values */
|
|
#define ETIMEDOUT -10
|
|
#define EINVAL -11
|
|
#define EIO -12
|
|
|
|
#define GSBI_IDX_TO_GSBI(idx) (idx + 5)
|
|
|
|
/* MX_INPUT_COUNT and MX_OUTPUT_COUNT are 16-bits. Zero has a special meaning
|
|
* (count function disabled) and does not hold significance in the count. */
|
|
#define MAX_PACKET_COUNT ((64 * KiB) - 1)
|
|
|
|
/*
|
|
* TLMM Configuration for SPI NOR
|
|
* gsbi_pin_conf[bus_num][GPIO_NUM, FUNC_SEL, I/O,
|
|
* PULL UP/DOWN, DRV_STR, GPIO_FUNC]
|
|
* gsbi_pin_conf[0][x][y] -- GSBI5
|
|
* gsbi_pin_conf[1][x][y] -- GSBI6
|
|
* gsbi_pin_conf[2][x][y] -- GSBI7
|
|
*/
|
|
static unsigned int gsbi_pin_conf[NUM_PORTS][NUM_GSBI_PINS][TLMM_ARGS] = {
|
|
{
|
|
/* GSBI5 CLK */
|
|
{
|
|
GSBI5_SPI_CLK, FUNC_SEL_1, GPIO_INPUT,
|
|
GPIO_PULL_DOWN, GPIO_DRV_STR_11MA, GPIO_FUNC_DISABLE
|
|
},
|
|
/* GSBI5 MISO */
|
|
{
|
|
GSBI5_SPI_MISO, FUNC_SEL_1, GPIO_INPUT,
|
|
GPIO_PULL_DOWN, GPIO_DRV_STR_10MA, GPIO_FUNC_DISABLE
|
|
},
|
|
/* GSBI5 MOSI */
|
|
{
|
|
GSBI5_SPI_MOSI, FUNC_SEL_1, GPIO_INPUT,
|
|
GPIO_PULL_DOWN, GPIO_DRV_STR_10MA, GPIO_FUNC_DISABLE
|
|
}
|
|
},
|
|
{
|
|
/* GSBI6 CLK */
|
|
{
|
|
GSBI6_SPI_CLK, FUNC_SEL_3, GPIO_INPUT,
|
|
GPIO_PULL_DOWN, GPIO_DRV_STR_11MA, GPIO_FUNC_DISABLE
|
|
},
|
|
/* GSBI6 MISO */
|
|
{
|
|
GSBI6_SPI_MISO, FUNC_SEL_3, GPIO_INPUT,
|
|
GPIO_PULL_DOWN, GPIO_DRV_STR_10MA, GPIO_FUNC_DISABLE
|
|
},
|
|
/* GSBI6 MOSI */
|
|
{
|
|
GSBI6_SPI_MOSI, FUNC_SEL_3, GPIO_INPUT,
|
|
GPIO_PULL_DOWN, GPIO_DRV_STR_10MA, GPIO_FUNC_DISABLE
|
|
}
|
|
},
|
|
{
|
|
/* GSBI7 CLK */
|
|
{
|
|
GSBI7_SPI_CLK, FUNC_SEL_1, GPIO_INPUT,
|
|
GPIO_PULL_DOWN, GPIO_DRV_STR_11MA, GPIO_FUNC_DISABLE
|
|
},
|
|
/* GSBI7 MISO */
|
|
{
|
|
GSBI7_SPI_MISO, FUNC_SEL_1, GPIO_INPUT,
|
|
GPIO_PULL_DOWN, GPIO_DRV_STR_10MA, GPIO_FUNC_DISABLE
|
|
},
|
|
/* GSBI7 MOSI */
|
|
{
|
|
GSBI7_SPI_MOSI, FUNC_SEL_1, GPIO_INPUT,
|
|
GPIO_PULL_DOWN, GPIO_DRV_STR_10MA, GPIO_FUNC_DISABLE
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
* CS GPIO number array cs_gpio_array[port_num][cs_num]
|
|
* cs_gpio_array[0][x] -- GSBI5
|
|
* cs_gpio_array[1][x] -- GSBI6
|
|
* cs_gpio_array[2][x] -- GSBI7
|
|
*/
|
|
static unsigned int cs_gpio_array[NUM_PORTS][NUM_CS] = {
|
|
{
|
|
GSBI5_SPI_CS_0, GSBI5_SPI_CS_1, GSBI5_SPI_CS_2, GSBI5_SPI_CS_3
|
|
},
|
|
{
|
|
GSBI6_SPI_CS_0, 0, 0, 0
|
|
},
|
|
{
|
|
GSBI7_SPI_CS_0, 0, 0, 0
|
|
}
|
|
};
|
|
|
|
/*
|
|
* GSBI HCLK state register bit
|
|
* hclk_state[0] -- GSBI5
|
|
* hclk_state[1] -- GSBI6
|
|
* hclk_state[2] -- GSBI7
|
|
*/
|
|
static unsigned int hclk_state[NUM_PORTS] = {
|
|
GSBI5_HCLK,
|
|
GSBI6_HCLK,
|
|
GSBI7_HCLK
|
|
};
|
|
|
|
/*
|
|
* GSBI QUP_APPS_CLK state register bit
|
|
* qup_apps_clk_state[0] -- GSBI5
|
|
* qup_apps_clk_state[1] -- GSBI6
|
|
* qup_apps_clk_state[2] -- GSBI7
|
|
*/
|
|
static unsigned int qup_apps_clk_state[NUM_PORTS] = {
|
|
GSBI5_QUP_APPS_CLK,
|
|
GSBI6_QUP_APPS_CLK,
|
|
GSBI7_QUP_APPS_CLK
|
|
};
|
|
|
|
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_i(reg_addr) >> bit_num) & 0x01);
|
|
|
|
while (bit_val != val) {
|
|
count--;
|
|
if (count == 0)
|
|
return -ETIMEDOUT;
|
|
udelay(us_delay);
|
|
bit_val = ((readl_i(reg_addr) >> bit_num) & 0x01);
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Check whether GSBIn_QUP 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 GSBIn Core state
|
|
*/
|
|
static int config_spi_state(struct ipq_spi_slave *ds, unsigned int state)
|
|
{
|
|
uint32_t val;
|
|
int ret;
|
|
uint32_t new_state;
|
|
|
|
ret = check_qup_state_valid(ds);
|
|
if (ret != SUCCESS)
|
|
return ret;
|
|
|
|
switch (state) {
|
|
case SPI_RUN_STATE:
|
|
new_state = QUP_STATE_RUN_STATE;
|
|
break;
|
|
|
|
case SPI_RESET_STATE:
|
|
new_state = QUP_STATE_RESET_STATE;
|
|
break;
|
|
|
|
case SPI_PAUSE_STATE:
|
|
new_state = QUP_STATE_PAUSE_STATE;
|
|
break;
|
|
|
|
default:
|
|
printk(BIOS_ERR,
|
|
"err: unsupported GSBI SPI state : %d\n", state);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Set the state as requested */
|
|
val = (readl_i(ds->regs->qup_state) & ~QUP_STATE_MASK)
|
|
| new_state;
|
|
writel_i(val, ds->regs->qup_state);
|
|
return check_qup_state_valid(ds);
|
|
}
|
|
|
|
/*
|
|
* Set GSBIn 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 GSBI_SPI_MODE_0:
|
|
clk_idle_state = 0;
|
|
input_first_mode = SPI_INPUT_FIRST_MODE;
|
|
break;
|
|
case GSBI_SPI_MODE_1:
|
|
clk_idle_state = 0;
|
|
input_first_mode = 0;
|
|
break;
|
|
case GSBI_SPI_MODE_2:
|
|
clk_idle_state = 1;
|
|
input_first_mode = SPI_INPUT_FIRST_MODE;
|
|
break;
|
|
case GSBI_SPI_MODE_3:
|
|
clk_idle_state = 1;
|
|
input_first_mode = 0;
|
|
break;
|
|
default:
|
|
printk(BIOS_ERR,
|
|
"err : unsupported spi mode : %d\n", mode);
|
|
return;
|
|
}
|
|
|
|
val = readl_i(ds->regs->spi_config);
|
|
val |= input_first_mode;
|
|
writel_i(val, ds->regs->spi_config);
|
|
|
|
val = readl_i(ds->regs->io_control);
|
|
if (clk_idle_state)
|
|
val |= SPI_IO_CONTROL_CLOCK_IDLE_HIGH;
|
|
else
|
|
val &= ~SPI_IO_CONTROL_CLOCK_IDLE_HIGH;
|
|
|
|
writel_i(val, ds->regs->io_control);
|
|
}
|
|
|
|
/*
|
|
* Check for HCLK state
|
|
*/
|
|
static int check_hclk_state(unsigned int core_num, int enable)
|
|
{
|
|
return check_bit_state(CLK_HALT_CFPB_STATEB_REG,
|
|
hclk_state[core_num], enable, 5);
|
|
}
|
|
|
|
/*
|
|
* Check for QUP APPS CLK state
|
|
*/
|
|
static int check_qup_clk_state(unsigned int core_num, int enable)
|
|
{
|
|
return check_bit_state(CLK_HALT_CFPB_STATEB_REG,
|
|
qup_apps_clk_state[core_num], enable, 5);
|
|
}
|
|
|
|
/*
|
|
* Function to assert and De-assert chip select
|
|
*/
|
|
static void CS_change(int port_num, int cs_num, int enable)
|
|
{
|
|
unsigned int cs_gpio = cs_gpio_array[port_num][cs_num];
|
|
void *addr = GPIO_IN_OUT_ADDR(cs_gpio);
|
|
uint32_t val = readl_i(addr);
|
|
|
|
val &= (~(1 << GPIO_OUTPUT));
|
|
if (!enable)
|
|
val |= (1 << GPIO_OUTPUT);
|
|
write32(addr, val);
|
|
}
|
|
|
|
/*
|
|
* GSBIn TLMM configuration
|
|
*/
|
|
static void gsbi_pin_config(unsigned int port_num, int cs_num)
|
|
{
|
|
unsigned int gpio;
|
|
unsigned int i;
|
|
/* Hold the GSBIn (core_num) core in reset */
|
|
clrsetbits32_i(GSBIn_RESET_REG(GSBI_IDX_TO_GSBI(port_num)),
|
|
GSBI1_RESET_MSK, GSBI1_RESET);
|
|
|
|
/*
|
|
* Configure SPI_CLK, SPI_MISO and SPI_MOSI
|
|
*/
|
|
for (i = 0; i < NUM_GSBI_PINS; i++) {
|
|
unsigned int func_sel;
|
|
unsigned int io_config;
|
|
unsigned int pull_config;
|
|
unsigned int drv_strength;
|
|
unsigned int gpio_en;
|
|
unsigned int *ptr;
|
|
|
|
ptr = gsbi_pin_conf[port_num][i];
|
|
gpio = *(ptr + GSBI_PIN_IDX);
|
|
func_sel = *(ptr + FUNC_SEL_IDX);
|
|
io_config = *(ptr + GPIO_DIR_IDX);
|
|
pull_config = *(ptr + PULL_CONF_IDX);
|
|
drv_strength = *(ptr + DRV_STR_IDX);
|
|
gpio_en = *(ptr + GPIO_EN_IDX);
|
|
|
|
gpio_tlmm_config(gpio, func_sel, io_config,
|
|
pull_config, drv_strength, gpio_en);
|
|
}
|
|
|
|
gpio = cs_gpio_array[port_num][cs_num];
|
|
/* configure CS */
|
|
gpio_tlmm_config(gpio, FUNC_SEL_GPIO, GPIO_OUTPUT, GPIO_PULL_UP,
|
|
GPIO_DRV_STR_10MA, GPIO_FUNC_ENABLE);
|
|
CS_change(port_num, cs_num, CS_DEASSERT);
|
|
}
|
|
|
|
/*
|
|
* Clock configuration for GSBIn Core
|
|
*/
|
|
static int gsbi_clock_init(struct ipq_spi_slave *ds)
|
|
{
|
|
int ret;
|
|
|
|
/* Hold the GSBIn (core_num) core in reset */
|
|
clrsetbits32_i(GSBIn_RESET_REG(GSBI_IDX_TO_GSBI(ds->slave.bus)),
|
|
GSBI1_RESET_MSK, GSBI1_RESET);
|
|
|
|
/* Disable GSBIn (core_num) QUP core clock branch */
|
|
clrsetbits32_i(ds->regs->qup_ns_reg, QUP_CLK_BRANCH_ENA_MSK,
|
|
QUP_CLK_BRANCH_DIS);
|
|
|
|
ret = check_qup_clk_state(ds->slave.bus, 1);
|
|
if (ret) {
|
|
printk(BIOS_ERR,
|
|
"QUP Clock Halt For GSBI%d failed!\n", ds->slave.bus);
|
|
return ret;
|
|
}
|
|
|
|
/* Disable M/N:D counter and hold M/N:D counter in reset */
|
|
clrsetbits32_i(ds->regs->qup_ns_reg, (MNCNTR_MSK | MNCNTR_RST_MSK),
|
|
(MNCNTR_RST_ENA | MNCNTR_DIS));
|
|
|
|
/* Disable GSBIn (core_num) QUP core clock root */
|
|
clrsetbits32_i(ds->regs->qup_ns_reg, CLK_ROOT_ENA_MSK, CLK_ROOT_DIS);
|
|
|
|
clrsetbits32_i(ds->regs->qup_ns_reg, GSBIn_PLL_SRC_MSK,
|
|
GSBIn_PLL_SRC_PLL8);
|
|
clrsetbits32_i(ds->regs->qup_ns_reg, GSBIn_PRE_DIV_SEL_MSK,
|
|
(0 << GSBI_PRE_DIV_SEL_SHFT));
|
|
|
|
/* Program M/N:D values for GSBIn_QUP_APPS_CLK @50MHz */
|
|
clrsetbits32_i(ds->regs->qup_md_reg, GSBIn_M_VAL_MSK,
|
|
(0x01 << GSBI_M_VAL_SHFT));
|
|
clrsetbits32_i(ds->regs->qup_md_reg, GSBIn_D_VAL_MSK,
|
|
(0xF7 << GSBI_D_VAL_SHFT));
|
|
clrsetbits32_i(ds->regs->qup_ns_reg, GSBIn_N_VAL_MSK,
|
|
(0xF8 << GSBI_N_VAL_SHFT));
|
|
|
|
/* Set MNCNTR_MODE = 0: Bypass mode */
|
|
clrsetbits32_i(ds->regs->qup_ns_reg, MNCNTR_MODE_MSK,
|
|
MNCNTR_MODE_DUAL_EDGE);
|
|
|
|
/* De-assert the M/N:D counter reset */
|
|
clrsetbits32_i(ds->regs->qup_ns_reg, MNCNTR_RST_MSK, MNCNTR_RST_DIS);
|
|
clrsetbits32_i(ds->regs->qup_ns_reg, MNCNTR_MSK, MNCNTR_EN);
|
|
|
|
/*
|
|
* Enable the GSBIn (core_num) QUP core clock root.
|
|
* Keep MND counter disabled
|
|
*/
|
|
clrsetbits32_i(ds->regs->qup_ns_reg, CLK_ROOT_ENA_MSK, CLK_ROOT_ENA);
|
|
|
|
/* Enable GSBIn (core_num) QUP core clock branch */
|
|
clrsetbits32_i(ds->regs->qup_ns_reg, QUP_CLK_BRANCH_ENA_MSK,
|
|
QUP_CLK_BRANCH_ENA);
|
|
|
|
ret = check_qup_clk_state(ds->slave.bus, 0);
|
|
if (ret) {
|
|
printk(BIOS_ERR,
|
|
"QUP Clock Enable For GSBI%d"
|
|
" failed!\n", ds->slave.bus);
|
|
return ret;
|
|
}
|
|
|
|
/* Enable GSBIn (core_num) core clock branch */
|
|
clrsetbits32_i(GSBIn_HCLK_CTL_REG(GSBI_IDX_TO_GSBI(ds->slave.bus)),
|
|
GSBI_CLK_BRANCH_ENA_MSK, GSBI_CLK_BRANCH_ENA);
|
|
|
|
ret = check_hclk_state(ds->slave.bus, 0);
|
|
if (ret) {
|
|
printk(BIOS_ERR,
|
|
"HCLK Enable For GSBI%d failed!\n", ds->slave.bus);
|
|
return ret;
|
|
}
|
|
|
|
/* Release GSBIn (core_num) core from reset */
|
|
clrsetbits32_i(GSBIn_RESET_REG(GSBI_IDX_TO_GSBI(ds->slave.bus)),
|
|
GSBI1_RESET_MSK, 0);
|
|
udelay(50);
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Reset entire QUP and all mini cores
|
|
*/
|
|
static void spi_reset(struct ipq_spi_slave *ds)
|
|
{
|
|
writel_i(0x1, ds->regs->qup_sw_reset);
|
|
udelay(5);
|
|
}
|
|
|
|
static const struct gsbi_spi spi_reg[] = {
|
|
/* GSBI5 registers for SPI interface */
|
|
{
|
|
GSBI5_SPI_CONFIG_REG,
|
|
GSBI5_SPI_IO_CONTROL_REG,
|
|
GSBI5_SPI_ERROR_FLAGS_REG,
|
|
GSBI5_SPI_ERROR_FLAGS_EN_REG,
|
|
GSBI5_GSBI_CTRL_REG_REG,
|
|
GSBI5_QUP_CONFIG_REG,
|
|
GSBI5_QUP_ERROR_FLAGS_REG,
|
|
GSBI5_QUP_ERROR_FLAGS_EN_REG,
|
|
GSBI5_QUP_OPERATIONAL_REG,
|
|
GSBI5_QUP_IO_MODES_REG,
|
|
GSBI5_QUP_STATE_REG,
|
|
GSBI5_QUP_INPUT_FIFOc_REG(0),
|
|
GSBI5_QUP_OUTPUT_FIFOc_REG(0),
|
|
GSBI5_QUP_MX_INPUT_COUNT_REG,
|
|
GSBI5_QUP_MX_OUTPUT_COUNT_REG,
|
|
GSBI5_QUP_SW_RESET_REG,
|
|
GSBIn_QUP_APPS_NS_REG(5),
|
|
GSBIn_QUP_APPS_MD_REG(5),
|
|
},
|
|
/* GSBI6 registers for SPI interface */
|
|
{
|
|
GSBI6_SPI_CONFIG_REG,
|
|
GSBI6_SPI_IO_CONTROL_REG,
|
|
GSBI6_SPI_ERROR_FLAGS_REG,
|
|
GSBI6_SPI_ERROR_FLAGS_EN_REG,
|
|
GSBI6_GSBI_CTRL_REG_REG,
|
|
GSBI6_QUP_CONFIG_REG,
|
|
GSBI6_QUP_ERROR_FLAGS_REG,
|
|
GSBI6_QUP_ERROR_FLAGS_EN_REG,
|
|
GSBI6_QUP_OPERATIONAL_REG,
|
|
GSBI6_QUP_IO_MODES_REG,
|
|
GSBI6_QUP_STATE_REG,
|
|
GSBI6_QUP_INPUT_FIFOc_REG(0),
|
|
GSBI6_QUP_OUTPUT_FIFOc_REG(0),
|
|
GSBI6_QUP_MX_INPUT_COUNT_REG,
|
|
GSBI6_QUP_MX_OUTPUT_COUNT_REG,
|
|
GSBI6_QUP_SW_RESET_REG,
|
|
GSBIn_QUP_APPS_NS_REG(6),
|
|
GSBIn_QUP_APPS_MD_REG(6),
|
|
},
|
|
/* GSBI7 registers for SPI interface */
|
|
{
|
|
GSBI7_SPI_CONFIG_REG,
|
|
GSBI7_SPI_IO_CONTROL_REG,
|
|
GSBI7_SPI_ERROR_FLAGS_REG,
|
|
GSBI7_SPI_ERROR_FLAGS_EN_REG,
|
|
GSBI7_GSBI_CTRL_REG_REG,
|
|
GSBI7_QUP_CONFIG_REG,
|
|
GSBI7_QUP_ERROR_FLAGS_REG,
|
|
GSBI7_QUP_ERROR_FLAGS_EN_REG,
|
|
GSBI7_QUP_OPERATIONAL_REG,
|
|
GSBI7_QUP_IO_MODES_REG,
|
|
GSBI7_QUP_STATE_REG,
|
|
GSBI7_QUP_INPUT_FIFOc_REG(0),
|
|
GSBI7_QUP_OUTPUT_FIFOc_REG(0),
|
|
GSBI7_QUP_MX_INPUT_COUNT_REG,
|
|
GSBI7_QUP_MX_OUTPUT_COUNT_REG,
|
|
GSBI7_QUP_SW_RESET_REG,
|
|
GSBIn_QUP_APPS_NS_REG(7),
|
|
GSBIn_QUP_APPS_MD_REG(7),
|
|
}
|
|
};
|
|
static struct ipq_spi_slave spi_slave_pool[2];
|
|
|
|
static struct ipq_spi_slave *to_ipq_spi(const struct spi_slave *slave)
|
|
{
|
|
struct ipq_spi_slave *ds;
|
|
size_t i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(spi_slave_pool); i++) {
|
|
ds = spi_slave_pool + i;
|
|
|
|
if (!ds->allocated)
|
|
continue;
|
|
|
|
if ((ds->slave.bus == slave->bus) &&
|
|
(ds->slave.cs == slave->cs))
|
|
return ds;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* GSBIn SPI Hardware Initialisation
|
|
*/
|
|
static int spi_hw_init(struct ipq_spi_slave *ds)
|
|
{
|
|
int ret;
|
|
|
|
if (ds->initialized)
|
|
return 0;
|
|
|
|
/* GSBI module configuration */
|
|
spi_reset(ds);
|
|
|
|
/* Set the GSBIn QUP state */
|
|
ret = config_spi_state(ds, SPI_RESET_STATE);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Configure GSBI_CTRL register to set protocol_mode to SPI:011 */
|
|
clrsetbits32_i(ds->regs->gsbi_ctrl, PROTOCOL_CODE_MSK,
|
|
PROTOCOL_CODE_SPI);
|
|
|
|
/*
|
|
* Configure Mini core to SPI core with Input Output enabled,
|
|
* SPI master, N = 8 bits
|
|
*/
|
|
clrsetbits32_i(ds->regs->qup_config, (QUP_CONFIG_MINI_CORE_MSK |
|
|
SPI_QUP_CONF_INPUT_MSK |
|
|
SPI_QUP_CONF_OUTPUT_MSK |
|
|
SPI_BIT_WORD_MSK),
|
|
(QUP_CONFIG_MINI_CORE_SPI |
|
|
SPI_QUP_CONF_NO_INPUT |
|
|
SPI_QUP_CONF_NO_OUTPUT |
|
|
SPI_8_BIT_WORD));
|
|
|
|
/*
|
|
* Configure Input first SPI protocol,
|
|
* SPI master mode and no loopback
|
|
*/
|
|
clrsetbits32_i(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_i((CLK_ALWAYS_ON | MX_CS_MODE | NO_TRI_STATE),
|
|
ds->regs->io_control);
|
|
|
|
/*
|
|
* Configure SPI IO Modes.
|
|
* OUTPUT_BIT_SHIFT_EN = 1
|
|
* INPUT_MODE = Block Mode
|
|
* OUTPUT MODE = Block Mode
|
|
*/
|
|
clrsetbits32_i(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_i(0, ds->regs->error_flags_en);
|
|
writel_i(0, ds->regs->qup_error_flags_en);
|
|
|
|
ds->initialized = 1;
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
static int spi_ctrlr_claim_bus(const struct spi_slave *slave)
|
|
{
|
|
struct ipq_spi_slave *ds = to_ipq_spi(slave);
|
|
unsigned int ret;
|
|
|
|
if (ds->initialized)
|
|
return SUCCESS;
|
|
|
|
/* GPIO Configuration for SPI port */
|
|
gsbi_pin_config(ds->slave.bus, ds->slave.cs);
|
|
|
|
/* Clock configuration */
|
|
ret = gsbi_clock_init(ds);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = spi_hw_init(ds);
|
|
if (ret)
|
|
return -EIO;
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
static void spi_ctrlr_release_bus(const struct spi_slave *slave)
|
|
{
|
|
struct ipq_spi_slave *ds = to_ipq_spi(slave);
|
|
|
|
/* Reset the SPI hardware */
|
|
spi_reset(ds);
|
|
ds->initialized = 0;
|
|
}
|
|
|
|
static int spi_xfer_tx_packet(struct ipq_spi_slave *ds,
|
|
const uint8_t *dout, unsigned int out_bytes)
|
|
{
|
|
int ret;
|
|
|
|
writel_i(out_bytes, ds->regs->qup_mx_output_count);
|
|
|
|
ret = config_spi_state(ds, SPI_RUN_STATE);
|
|
if (ret)
|
|
return ret;
|
|
|
|
while (out_bytes) {
|
|
if (readl_i(ds->regs->qup_operational) & QUP_OUTPUT_FIFO_FULL)
|
|
continue;
|
|
|
|
writel_i(*dout++, ds->regs->qup_output_fifo);
|
|
out_bytes--;
|
|
|
|
/* Wait for output FIFO to drain. */
|
|
if (!out_bytes)
|
|
while (readl_i(ds->regs->qup_operational) &
|
|
QUP_OUTPUT_FIFO_NOT_EMPTY)
|
|
;
|
|
}
|
|
|
|
return config_spi_state(ds, SPI_RESET_STATE);
|
|
}
|
|
|
|
static int spi_xfer_rx_packet(struct ipq_spi_slave *ds,
|
|
uint8_t *din, unsigned int in_bytes)
|
|
{
|
|
int ret;
|
|
|
|
writel_i(in_bytes, ds->regs->qup_mx_input_count);
|
|
writel_i(in_bytes, ds->regs->qup_mx_output_count);
|
|
|
|
ret = config_spi_state(ds, SPI_RUN_STATE);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Seed clocking */
|
|
writel_i(0xff, ds->regs->qup_output_fifo);
|
|
while (in_bytes) {
|
|
if (!(readl_i(ds->regs->qup_operational) &
|
|
QUP_INPUT_FIFO_NOT_EMPTY))
|
|
continue;
|
|
/* Keep it clocking */
|
|
writel_i(0xff, ds->regs->qup_output_fifo);
|
|
|
|
*din++ = readl_i(ds->regs->qup_input_fifo) & 0xff;
|
|
in_bytes--;
|
|
}
|
|
|
|
return config_spi_state(ds, SPI_RESET_STATE);
|
|
}
|
|
|
|
static int spi_ctrlr_xfer(const struct spi_slave *slave, const void *dout,
|
|
size_t out_bytes, void *din, size_t in_bytes)
|
|
{
|
|
int ret;
|
|
struct ipq_spi_slave *ds = to_ipq_spi(slave);
|
|
|
|
/* Assert the chip select */
|
|
CS_change(ds->slave.bus, ds->slave.cs, CS_ASSERT);
|
|
|
|
ret = config_spi_state(ds, SPI_RESET_STATE);
|
|
if (ret)
|
|
goto out;
|
|
|
|
if (!out_bytes)
|
|
goto spi_receive;
|
|
|
|
/*
|
|
* Let's do the write side of the transaction first. Enable output
|
|
* FIFO.
|
|
*/
|
|
clrsetbits32_i(ds->regs->qup_config, SPI_QUP_CONF_OUTPUT_MSK,
|
|
SPI_QUP_CONF_OUTPUT_ENA);
|
|
|
|
while (out_bytes) {
|
|
unsigned int todo = MIN(out_bytes, MAX_PACKET_COUNT);
|
|
|
|
ret = spi_xfer_tx_packet(ds, dout, todo);
|
|
if (ret)
|
|
break;
|
|
|
|
out_bytes -= todo;
|
|
dout += todo;
|
|
}
|
|
|
|
if (ret)
|
|
goto out;
|
|
|
|
spi_receive:
|
|
if (!in_bytes) /* Nothing to read. */
|
|
goto out;
|
|
|
|
/* Enable input FIFO */
|
|
clrsetbits32_i(ds->regs->qup_config, SPI_QUP_CONF_INPUT_MSK,
|
|
SPI_QUP_CONF_INPUT_ENA);
|
|
|
|
while (in_bytes) {
|
|
unsigned int todo = MIN(in_bytes, MAX_PACKET_COUNT);
|
|
|
|
ret = spi_xfer_rx_packet(ds, din, todo);
|
|
if (ret)
|
|
break;
|
|
|
|
in_bytes -= todo;
|
|
din += todo;
|
|
}
|
|
|
|
out:
|
|
/* Deassert CS */
|
|
CS_change(ds->slave.bus, ds->slave.cs, CS_DEASSERT);
|
|
|
|
/*
|
|
* 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 spi_ctrlr_setup(const struct spi_slave *slave)
|
|
{
|
|
struct ipq_spi_slave *ds = NULL;
|
|
int i;
|
|
int bus = slave->bus;
|
|
int cs = slave->cs;
|
|
|
|
/*
|
|
* IPQ GSBI (Generic Serial Bus Interface) supports SPI Flash
|
|
* on different GSBI5, GSBI6 and GSBI7
|
|
* with different number of chip selects (CS, channels):
|
|
*/
|
|
if ((bus < GSBI5_SPI) || (bus > GSBI7_SPI)
|
|
|| ((bus == GSBI5_SPI) && (cs > 3))
|
|
|| ((bus == GSBI6_SPI) && (cs > 0))
|
|
|| ((bus == GSBI7_SPI) && (cs > 0))) {
|
|
printk(BIOS_ERR, "SPI error: unsupported bus %d "
|
|
"(Supported busses 0,1 and 2) or chipselect\n", bus);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(spi_slave_pool); i++) {
|
|
if (spi_slave_pool[i].allocated)
|
|
continue;
|
|
ds = spi_slave_pool + i;
|
|
|
|
ds->slave.bus = bus;
|
|
ds->slave.cs = cs;
|
|
ds->regs = &spi_reg[bus];
|
|
|
|
/*
|
|
* TODO(vbendeb):
|
|
* hardcoded frequency and mode - we might need to find a way
|
|
* to configure this
|
|
*/
|
|
ds->freq = 10000000;
|
|
ds->mode = GSBI_SPI_MODE_0;
|
|
ds->allocated = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
printk(BIOS_ERR, "SPI error: all %d pools busy\n", i);
|
|
return -1;
|
|
}
|
|
|
|
static const struct spi_ctrlr spi_ctrlr = {
|
|
.setup = spi_ctrlr_setup,
|
|
.claim_bus = spi_ctrlr_claim_bus,
|
|
.release_bus = spi_ctrlr_release_bus,
|
|
.xfer = spi_ctrlr_xfer,
|
|
.max_xfer_size = MAX_PACKET_COUNT,
|
|
};
|
|
|
|
const struct spi_ctrlr_buses spi_ctrlr_bus_map[] = {
|
|
{
|
|
.ctrlr = &spi_ctrlr,
|
|
.bus_start = GSBI5_SPI,
|
|
.bus_end = GSBI7_SPI,
|
|
},
|
|
};
|
|
|
|
const size_t spi_ctrlr_bus_map_count = ARRAY_SIZE(spi_ctrlr_bus_map);
|