mturney mturney has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/36517 )
Change subject: sc7180: Add SPI QUP driver ......................................................................
sc7180: Add SPI QUP driver
This implements the SPI driver for the QUP core.
Change-Id: I86f4fcff6f9537373f70a43711130d7f28bd5e09 Signed-off-by: Roja Rani Yarubandi rojay@codeaurora.org --- M src/mainboard/google/trogdor/bootblock.c M src/soc/qualcomm/sc7180/Makefile.inc A src/soc/qualcomm/sc7180/include/soc/spi_qup_qcom.h M src/soc/qualcomm/sc7180/qcom_qup_se.c M src/soc/qualcomm/sc7180/spi.c A src/soc/qualcomm/sc7180/spi_qup.c 6 files changed, 310 insertions(+), 6 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/17/36517/1
diff --git a/src/mainboard/google/trogdor/bootblock.c b/src/mainboard/google/trogdor/bootblock.c index c658093..a109a08 100644 --- a/src/mainboard/google/trogdor/bootblock.c +++ b/src/mainboard/google/trogdor/bootblock.c @@ -15,8 +15,12 @@
#include <bootblock_common.h> #include "board.h" +#include <soc/qcom_qup_se.h> +#include <soc/spi_qup_qcom.h>
void bootblock_mainboard_init(void) { setup_chromeos_gpios(); + qup_spi_init(QUPV3_0_SE0, 1010 * KHz); /* H1 SPI */ + qup_spi_init(QUPV3_1_SE4, 1010 * KHz); /* EC SPI */ } diff --git a/src/soc/qualcomm/sc7180/Makefile.inc b/src/soc/qualcomm/sc7180/Makefile.inc index 18f8ada..66cb35c 100644 --- a/src/soc/qualcomm/sc7180/Makefile.inc +++ b/src/soc/qualcomm/sc7180/Makefile.inc @@ -6,6 +6,7 @@ bootblock-y += mmu.c bootblock-y += timer.c bootblock-y += spi.c +bootblock-y += spi_qup.c bootblock-y += gpio.c bootblock-$(CONFIG_DRIVERS_UART) += uart_bitbang.c bootblock-y += clock.c @@ -17,6 +18,7 @@ ################################################################################ verstage-y += timer.c verstage-y += spi.c +verstage-y += spi_qup.c verstage-y += gpio.c verstage-y += clock.c verstage-y += qcom_qup_se.c @@ -32,6 +34,7 @@ romstage-y += mmu.c romstage-y += usb.c romstage-y += spi.c +romstage-y += spi_qup.c romstage-y += gpio.c romstage-y += clock.c romstage-y += qcom_qup_se.c @@ -43,6 +46,7 @@ ramstage-y += cbmem.c ramstage-y += timer.c ramstage-y += spi.c +ramstage-y += spi_qup.c ramstage-y += gpio.c ramstage-y += clock.c ramstage-y += qupv3_fw_config.c diff --git a/src/soc/qualcomm/sc7180/include/soc/spi_qup_qcom.h b/src/soc/qualcomm/sc7180/include/soc/spi_qup_qcom.h new file mode 100644 index 0000000..4999422 --- /dev/null +++ b/src/soc/qualcomm/sc7180/include/soc/spi_qup_qcom.h @@ -0,0 +1,27 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (c) 2018-2019 Qualcomm Technologies + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __SPI_QUP_QCOM_HEADER___ +#define __SPI_QUP_QCOM_HEADER___ + +#include <spi-generic.h> + +int qup_spi_claim_bus(const struct spi_slave *slave); +int qup_spi_xfer(const struct spi_slave *slave, const void *dout, + size_t bytes_out, void *din, size_t bytes_in); +void qup_spi_release_bus(const struct spi_slave *slave); +void qup_spi_init(unsigned int bus, unsigned int speed_hz); + +#endif /*__SPI_QUP_QCOM_HEADER___*/ diff --git a/src/soc/qualcomm/sc7180/qcom_qup_se.c b/src/soc/qualcomm/sc7180/qcom_qup_se.c index 1774a2c..d14a852 100644 --- a/src/soc/qualcomm/sc7180/qcom_qup_se.c +++ b/src/soc/qualcomm/sc7180/qcom_qup_se.c @@ -92,7 +92,6 @@ if (m_irq) break; } - write32(®s->geni_m_irq_clear, m_irq); return m_irq; }
@@ -147,17 +146,38 @@ void qup_handle_error(unsigned int bus) { struct qup_regs *regs = qup[bus].regs; + struct stopwatch sw; + unsigned int m_irq;
write32(®s->geni_tx_watermark_reg, 0); write32(®s->geni_m_cmd_ctrl_reg, M_GENI_CMD_CANCEL);
- if (!wait_us(100, qup_wait_for_irq(bus) & M_CMD_CANCEL_EN)) { + stopwatch_init_msecs_expire(&sw, 100); + do { + m_irq = qup_wait_for_irq(bus); + if (m_irq & M_CMD_CANCEL_EN) { + write32(®s->geni_m_irq_clear, m_irq); + break; + } + write32(®s->geni_m_irq_clear, m_irq); + } while (!stopwatch_expired(&sw)); + + if (!(m_irq & M_CMD_CANCEL_EN)) { printk(BIOS_INFO, "%s:Cancel failed" ",Abort the operation\n", __func__);
- write32(®s->geni_m_cmd_ctrl_reg, - M_GENI_CMD_ABORT); - if (!wait_us(100, qup_wait_for_irq(bus) & M_CMD_ABORT_EN)) + write32(®s->geni_m_cmd_ctrl_reg, M_GENI_CMD_ABORT); + stopwatch_init_msecs_expire(&sw, 100); + do { + m_irq = qup_wait_for_irq(bus); + if (m_irq & M_CMD_ABORT_EN) { + write32(®s->geni_m_irq_clear, m_irq); + break; + } + write32(®s->geni_m_irq_clear, m_irq); + } while (!stopwatch_expired(&sw)); + + if (!(m_irq & M_CMD_ABORT_EN)) printk(BIOS_INFO, "%s:Abort failed\n", __func__); } } @@ -168,6 +188,7 @@ struct stopwatch sw; unsigned int rx_rem_bytes = din ? size : 0; unsigned int tx_rem_bytes = dout ? size : 0; + struct qup_regs *regs = qup[bus].regs;
stopwatch_init_msecs_expire(&sw, 1000); do { @@ -179,8 +200,11 @@ if (m_irq & M_TX_FIFO_WATERMARK_EN) tx_rem_bytes -= handle_tx(bus, dout + size - tx_rem_bytes, tx_rem_bytes); - if (m_irq & M_CMD_DONE_EN) + if (m_irq & M_CMD_DONE_EN) { + write32(®s->geni_m_irq_clear, m_irq); break; + } + write32(®s->geni_m_irq_clear, m_irq); } while (!stopwatch_expired(&sw));
if (!(m_irq & M_CMD_DONE_EN) || tx_rem_bytes || rx_rem_bytes) { diff --git a/src/soc/qualcomm/sc7180/spi.c b/src/soc/qualcomm/sc7180/spi.c index c6d4cb1..4146032 100644 --- a/src/soc/qualcomm/sc7180/spi.c +++ b/src/soc/qualcomm/sc7180/spi.c @@ -16,6 +16,7 @@ #include <spi-generic.h> #include <spi_flash.h> #include <soc/qspi.h> +#include <soc/spi_qup_qcom.h>
static const struct spi_ctrlr qspi_ctrlr = { .claim_bus = sc7180_claim_bus, @@ -25,12 +26,24 @@ .max_xfer_size = QSPI_MAX_PACKET_COUNT, };
+const struct spi_ctrlr spi_qup_ctrlr = { + .claim_bus = qup_spi_claim_bus, + .release_bus = qup_spi_release_bus, + .xfer = qup_spi_xfer, + .max_xfer_size = 65535, +}; + const struct spi_ctrlr_buses spi_ctrlr_bus_map[] = { { .ctrlr = &qspi_ctrlr, .bus_start = CONFIG_BOOT_DEVICE_SPI_FLASH_BUS, .bus_end = CONFIG_BOOT_DEVICE_SPI_FLASH_BUS, }, + { + .ctrlr = &spi_qup_ctrlr, + .bus_start = 0, + .bus_end = 11, + }, };
const size_t spi_ctrlr_bus_map_count = ARRAY_SIZE(spi_ctrlr_bus_map); diff --git a/src/soc/qualcomm/sc7180/spi_qup.c b/src/soc/qualcomm/sc7180/spi_qup.c new file mode 100644 index 0000000..6953c6a --- /dev/null +++ b/src/soc/qualcomm/sc7180/spi_qup.c @@ -0,0 +1,232 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2018-2019, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <assert.h> +#include <delay.h> +#include <lib.h> +#include <soc/clock.h> +#include <soc/gpio.h> +#include <soc/qcom_qup_se.h> +#include <soc/spi_qup_qcom.h> + +/* SE_SPI_LOOPBACK register fields */ +#define LOOPBACK_ENABLE 0x1 + +/* SE_SPI_WORD_LEN register fields */ +#define WORD_LEN_MSK GENMASK(9, 0) +#define MIN_WORD_LEN 4 + +/* SPI_TX/SPI_RX_TRANS_LEN fields */ +#define TRANS_LEN_MSK GENMASK(23, 0) + +/* M_CMD OP codes for SPI */ +#define SPI_TX_ONLY 1 +#define SPI_RX_ONLY 2 +#define SPI_FULL_DUPLEX 3 +#define SPI_TX_RX 7 +#define SPI_CS_ASSERT 8 +#define SPI_CS_DEASSERT 9 +#define SPI_SCK_ONLY 10 + +/* M_CMD params for SPI */ +/* If fragmentation bit is set then CS will not toggle after each transfer */ +#define M_CMD_FRAGMENTATION BIT(2) + +#define BITS_PER_BYTE 8 +#define BITS_PER_WORD 8 +#define TX_WATERMARK 1 + +#define IRQ_TRIGGER (M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN | \ + M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN | \ + M_CMD_CANCEL_EN | M_CMD_ABORT_EN) + +static void setup_fifo_params(const struct spi_slave *slave) +{ + unsigned int se_bus = slave->bus; + struct qup_regs *regs = qup[se_bus].regs; + u32 word_len = 0; + + /* Disable loopback mode */ + write32(®s->proto_loopback_cfg, 0); + + write32(®s->spi_demux_sel, slave->cs); + word_len = ((BITS_PER_WORD - MIN_WORD_LEN) & WORD_LEN_MSK); + write32(®s->word_len.spi_word_len, word_len); + + /* FIFO PACKING CONFIGURATION */ + write32(®s->geni_tx_packing_cfg0, PACK_VECTOR0 + | (PACK_VECTOR1 << 10)); + write32(®s->geni_tx_packing_cfg1, PACK_VECTOR2 + | (PACK_VECTOR3 << 10)); + write32(®s->geni_rx_packing_cfg0, PACK_VECTOR0 + | (PACK_VECTOR1 << 10)); + write32(®s->geni_rx_packing_cfg1, PACK_VECTOR2 + | (PACK_VECTOR3 << 10)); + write32(®s->geni_byte_granularity, (log2(BITS_PER_WORD) - 3)); +} + +static void qup_setup_m_cmd(unsigned int se_bus, u32 cmd, u32 params) +{ + struct qup_regs *regs = qup[se_bus].regs; + u32 m_cmd = (cmd << M_OPCODE_SHFT); + + m_cmd |= (params & M_PARAMS_MSK); + write32(®s->geni_m_cmd0, m_cmd); +} + +int qup_spi_xfer(const struct spi_slave *slave, const void *dout, + size_t bytes_out, void *din, size_t bytes_in) +{ + u32 m_cmd = 0; + u32 m_param = M_CMD_FRAGMENTATION; + int size; + unsigned int se_bus = slave->bus; + struct qup_regs *regs = qup[se_bus].regs; + + if ((bytes_in == 0) && (bytes_out == 0)) + return 0; + + setup_fifo_params(slave); + + if (!bytes_out) { + size = bytes_in; + m_cmd = SPI_RX_ONLY; + dout = NULL; + } else if (!bytes_in) { + size = bytes_out; + m_cmd = SPI_TX_ONLY; + din = NULL; + } else { + size = MIN(bytes_in, bytes_out); + m_cmd = SPI_FULL_DUPLEX; + } + + /* Check for maximum permissible transfer length */ + assert(!(size & ~TRANS_LEN_MSK)); + + if (bytes_out) { + write32(®s->tx_trans_len.spi_tx_trans_len, size); + write32(®s->geni_tx_watermark_reg, TX_WATERMARK); + } + if (bytes_in) + write32(®s->rx_trans_len.spi_rx_trans_len, size); + + qup_setup_m_cmd(se_bus, m_cmd, m_param); + + if (qup_handle_transfer(se_bus, dout, din, size)) + return -1; + + qup_spi_xfer(slave, dout + size, MAX((int)bytes_out - size, 0), + din + size, MAX((int)bytes_in - size, 0)); + + return 0; +} + +static int spi_qup_set_cs(const struct spi_slave *slave, bool enable) +{ + u32 m_cmd = 0; + u32 m_irq = 0; + unsigned int se_bus = slave->bus; + struct stopwatch sw; + + m_cmd = (enable) ? SPI_CS_ASSERT : SPI_CS_DEASSERT; + qup_setup_m_cmd(se_bus, m_cmd, 0); + + stopwatch_init_usecs_expire(&sw, 100); + do { + m_irq = qup_wait_for_irq(se_bus); + if (m_irq & M_CMD_DONE_EN) { + write32(&qup[se_bus].regs->geni_m_irq_clear, m_irq); + break; + } + write32(&qup[se_bus].regs->geni_m_irq_clear, m_irq); + } while (!stopwatch_expired(&sw)); + + if (!(m_irq & M_CMD_DONE_EN)) { + printk(BIOS_INFO, "%s:Failed to %s chip\n", __func__, + (enable) ? "Assert" : "Deassert"); + qup_handle_error(se_bus); + return -1; + } + return 0; +} + +void qup_spi_init(unsigned int bus, unsigned int speed_hz) +{ + u32 m_clk_cfg = 0, div = DEFAULT_SE_CLK / speed_hz; + struct qup_regs *regs = qup[bus].regs; + + /* Make sure div can hit target frequency within +/- 1KHz range */ + assert(((DEFAULT_SE_CLK - speed_hz * div) <= div * KHz) && (div > 0)); + clock_enable_qup(bus); + m_clk_cfg |= ((div << CLK_DIV_SHFT) | SER_CLK_EN); + write32(®s->geni_ser_m_clk_cfg, m_clk_cfg); + /* Mode:0, cpha=0, cpol=0 */ + write32(®s->spi_cpha, 0); + write32(®s->spi_cpol, 0); + /* Disable DFS */ + clrbits_le32(®s->geni_dfs_if_cfg, + GENI_DFS_IF_CFG_DFS_IF_EN_BMSK); + + /* Serial engine IO initialization */ + write32(®s->geni_cgc_ctrl, DEFAULT_CGC_EN); + write32(®s->dma_general_cfg, + (AHB_SEC_SLV_CLK_CGC_ON | DMA_AHB_SLV_CFG_ON + | DMA_TX_CLK_CGC_ON | DMA_RX_CLK_CGC_ON)); + write32(®s->geni_output_ctrl, + DEFAULT_IO_OUTPUT_CTRL_MSK); + write32(®s->geni_force_default_reg, FORCE_DEFAULT); + + /* Serial engine IO set mode */ + write32(®s->se_irq_en, (GENI_M_IRQ_EN | + GENI_S_IRQ_EN | DMA_TX_IRQ_EN | DMA_RX_IRQ_EN)); + write32(®s->se_gsi_event_en, 0); + + /* Set RX and RFR watermark */ + write32(®s->geni_rx_watermark_reg, 0); + write32(®s->geni_rx_rfr_watermark_reg, FIFO_DEPTH - 2); + + /* GPIO Configuration */ + gpio_configure(qup[bus].pin[0], qup[bus].func[0], GPIO_NO_PULL, + GPIO_6MA, GPIO_INPUT); /* MISO */ + gpio_configure(qup[bus].pin[1], qup[bus].func[1], GPIO_NO_PULL, + GPIO_6MA, GPIO_OUTPUT); /* MOSI */ + gpio_configure(qup[bus].pin[2], qup[bus].func[2], GPIO_NO_PULL, + GPIO_6MA, GPIO_OUTPUT); /* CLK */ + gpio_configure(qup[bus].pin[3], qup[bus].func[3], GPIO_NO_PULL, + GPIO_6MA, GPIO_OUTPUT); /* CS */ + + /* Select and setup FIFO mode */ + write32(®s->geni_m_irq_clear, 0xFFFFFFFF); + write32(®s->geni_s_irq_clear, 0xFFFFFFFF); + write32(®s->dma_tx_irq_clr, 0xFFFFFFFF); + write32(®s->dma_rx_irq_clr, 0xFFFFFFFF); + write32(®s->geni_m_irq_enable, (M_COMMON_GENI_M_IRQ_EN | + M_CMD_DONE_EN | M_TX_FIFO_WATERMARK_EN | + M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN)); + write32(®s->geni_s_irq_enable, (S_COMMON_GENI_S_IRQ_EN + | S_CMD_DONE_EN)); + clrbits_le32(®s->geni_dma_mode_en, GENI_DMA_MODE_EN); +} + +int qup_spi_claim_bus(const struct spi_slave *slave) +{ + return spi_qup_set_cs(slave, 1); +} + +void qup_spi_release_bus(const struct spi_slave *slave) +{ + spi_qup_set_cs(slave, 0); +}