diff options
author | Varadarajan Narayanan <varada@codeaurora.org> | 2016-03-02 16:57:10 +0530 |
---|---|---|
committer | Patrick Georgi <pgeorgi@google.com> | 2016-05-10 21:34:21 +0200 |
commit | a6935c2508c426f30d6bf5bcf4c3130277a0f998 (patch) | |
tree | e844bb803e8c069101fdf6ec47017af9dc832713 /src/soc/qualcomm/ipq40xx/spi.c | |
parent | c84e2fe893e02de3c5d97d26d05462351ac90d91 (diff) | |
download | coreboot-a6935c2508c426f30d6bf5bcf4c3130277a0f998.tar.xz |
soc/qualcomm/ipq40xx: Initial commit for IPQ40xx SoC support
Copy 'ipq806x' files as a template
BUG=chrome-os-partner:49249
TEST=None. Initial code not sure if it will even compile
BRANCH=none
Original-Commit-Id: dc6a5937953fe61cd4b5a99ca49f9371c4b712d4
Original-Change-Id: If171fcdd3b0561cb6b7dab5f8434de7ef711ea41
Original-Signed-off-by: Varadarajan Narayanan <varada@codeaurora.org>
Original-Signed-off-by: Kan Yan <kyan@google.com>
Original-Reviewed-on: https://chromium-review.googlesource.com/333178
Original-Commit-Ready: David Hendricks <dhendrix@chromium.org>
Original-Tested-by: David Hendricks <dhendrix@chromium.org>
Original-Reviewed-by: David Hendricks <dhendrix@chromium.org>
squashed:
soc/qualcomm/ipq40xx: Update ipq806x/storm references
Since the files were taken from ipq806x/storm as
template. Update those references to reflect
ipq40xx/gale.
BUG=chrome-os-partner:49249
TEST=None. Initial code not sure if it will even compile
BRANCH=none
Original-Commit-Id: c6c76d184cc92c09e6826fbdc7d7fac59b2cb69b
Original-Change-Id: Ieae1bce25291243b4a6034d37a6949978f318997
Original-Signed-off-by: Varadarajan Narayanan <varada@codeaurora.org>
Original-Reviewed-on: https://chromium-review.googlesource.com/333293
Original-Commit-Ready: David Hendricks <dhendrix@chromium.org>
Original-Tested-by: David Hendricks <dhendrix@chromium.org>
Original-Reviewed-by: David Hendricks <dhendrix@chromium.org>
Change-Id: Ie5794c48131ae562861074b406106734541880d9
Signed-off-by: Patrick Georgi <pgeorgi@chromium.org>
Reviewed-on: https://review.coreboot.org/14644
Tested-by: build bot (Jenkins)
Reviewed-by: Martin Roth <martinroth@google.com>
Diffstat (limited to 'src/soc/qualcomm/ipq40xx/spi.c')
-rw-r--r-- | src/soc/qualcomm/ipq40xx/spi.c | 805 |
1 files changed, 805 insertions, 0 deletions
diff --git a/src/soc/qualcomm/ipq40xx/spi.c b/src/soc/qualcomm/ipq40xx/spi.c new file mode 100644 index 0000000000..be98fba665 --- /dev/null +++ b/src/soc/qualcomm/ipq40xx/spi.c @@ -0,0 +1,805 @@ +/* + * Copyright (c) 2012 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 <arch/io.h> +#include <delay.h> +#include <gpio.h> +#include <soc/iomap.h> +#include <soc/spi.h> +#include <stdlib.h> +#include <string.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 */ + clrsetbits_le32_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 */ + clrsetbits_le32_i(GSBIn_RESET_REG(GSBI_IDX_TO_GSBI(ds->slave.bus)), + GSBI1_RESET_MSK, GSBI1_RESET); + + /* Disable GSBIn (core_num) QUP core clock branch */ + clrsetbits_le32_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 */ + clrsetbits_le32_i(ds->regs->qup_ns_reg, (MNCNTR_MSK | MNCNTR_RST_MSK), + (MNCNTR_RST_ENA | MNCNTR_DIS)); + + /* Disable GSBIn (core_num) QUP core clock root */ + clrsetbits_le32_i(ds->regs->qup_ns_reg, CLK_ROOT_ENA_MSK, CLK_ROOT_DIS); + + clrsetbits_le32_i(ds->regs->qup_ns_reg, GSBIn_PLL_SRC_MSK, + GSBIn_PLL_SRC_PLL8); + clrsetbits_le32_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 */ + clrsetbits_le32_i(ds->regs->qup_md_reg, GSBIn_M_VAL_MSK, + (0x01 << GSBI_M_VAL_SHFT)); + clrsetbits_le32_i(ds->regs->qup_md_reg, GSBIn_D_VAL_MSK, + (0xF7 << GSBI_D_VAL_SHFT)); + clrsetbits_le32_i(ds->regs->qup_ns_reg, GSBIn_N_VAL_MSK, + (0xF8 << GSBI_N_VAL_SHFT)); + + /* Set MNCNTR_MODE = 0: Bypass mode */ + clrsetbits_le32_i(ds->regs->qup_ns_reg, MNCNTR_MODE_MSK, + MNCNTR_MODE_DUAL_EDGE); + + /* De-assert the M/N:D counter reset */ + clrsetbits_le32_i(ds->regs->qup_ns_reg, MNCNTR_RST_MSK, MNCNTR_RST_DIS); + clrsetbits_le32_i(ds->regs->qup_ns_reg, MNCNTR_MSK, MNCNTR_EN); + + /* + * Enable the GSBIn (core_num) QUP core clock root. + * Keep MND counter disabled + */ + clrsetbits_le32_i(ds->regs->qup_ns_reg, CLK_ROOT_ENA_MSK, CLK_ROOT_ENA); + + /* Enable GSBIn (core_num) QUP core clock branch */ + clrsetbits_le32_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 */ + clrsetbits_le32_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 */ + clrsetbits_le32_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]; + +void spi_init(void) +{ + /* just in case */ + memset(spi_slave_pool, 0, sizeof(spi_slave_pool)); +} + +struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs) +{ + struct ipq_spi_slave *ds = NULL; + int i; + + /* + * 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 &ds->slave; + } + + printk(BIOS_ERR, "SPI error: all %d pools busy\n", i); + 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 */ + clrsetbits_le32_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 + */ + clrsetbits_le32_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 + */ + clrsetbits_le32_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 + */ + clrsetbits_le32_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; +} + +int spi_claim_bus(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; +} + +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 int spi_xfer_tx_packet(struct ipq_spi_slave *ds, + const uint8_t *dout, unsigned 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 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); +} + +unsigned int spi_crop_chunk(unsigned int cmd_len, unsigned int buf_len) +{ + return min(MAX_PACKET_COUNT, buf_len); +} + +int spi_xfer(struct spi_slave *slave, const void *dout, + unsigned out_bytes, void *din, unsigned 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. + */ + clrsetbits_le32_i(ds->regs->qup_config, SPI_QUP_CONF_OUTPUT_MSK, + SPI_QUP_CONF_OUTPUT_ENA); + + while (out_bytes) { + unsigned 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 */ + clrsetbits_le32_i(ds->regs->qup_config, SPI_QUP_CONF_INPUT_MSK, + SPI_QUP_CONF_INPUT_ENA); + + while (in_bytes) { + unsigned 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; +} |