aboutsummaryrefslogtreecommitdiff
path: root/src/soc/qualcomm/sc7180/qupv3_spi.c
diff options
context:
space:
mode:
authorT Michael Turney <mturney@codeaurora.org>2019-11-27 19:22:45 -0800
committerJulius Werner <jwerner@chromium.org>2020-04-21 21:54:21 +0000
commit47a0832f821ab0e005f3552d0a6a26624c78a0c0 (patch)
tree546ed679d6241989ae948713828897f8df43d67c /src/soc/qualcomm/sc7180/qupv3_spi.c
parent7ae833bdaa3778d476f5d8a0a123c3492ceecef8 (diff)
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> Reviewed-on: https://review.coreboot.org/c/coreboot/+/36517 Reviewed-by: Julius Werner <jwerner@chromium.org> Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Diffstat (limited to 'src/soc/qualcomm/sc7180/qupv3_spi.c')
-rw-r--r--src/soc/qualcomm/sc7180/qupv3_spi.c231
1 files changed, 231 insertions, 0 deletions
diff --git a/src/soc/qualcomm/sc7180/qupv3_spi.c b/src/soc/qualcomm/sc7180/qupv3_spi.c
new file mode 100644
index 0000000000..b6999056fb
--- /dev/null
+++ b/src/soc/qualcomm/sc7180/qupv3_spi.c
@@ -0,0 +1,231 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2018-2020, 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/qupv3_config.h>
+#include <soc/qupv3_spi.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(&regs->proto_loopback_cfg, 0);
+
+ write32(&regs->spi_demux_sel, slave->cs);
+ word_len = ((BITS_PER_WORD - MIN_WORD_LEN) & WORD_LEN_MSK);
+ write32(&regs->spi_word_len, word_len);
+
+ /* FIFO PACKING CONFIGURATION */
+ write32(&regs->geni_tx_packing_cfg0, PACK_VECTOR0
+ | (PACK_VECTOR1 << 10));
+ write32(&regs->geni_tx_packing_cfg1, PACK_VECTOR2
+ | (PACK_VECTOR3 << 10));
+ write32(&regs->geni_rx_packing_cfg0, PACK_VECTOR0
+ | (PACK_VECTOR1 << 10));
+ write32(&regs->geni_rx_packing_cfg1, PACK_VECTOR2
+ | (PACK_VECTOR3 << 10));
+ write32(&regs->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(&regs->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(&regs->spi_tx_trans_len, size);
+ write32(&regs->geni_tx_watermark_reg, TX_WATERMARK);
+ }
+ if (bytes_in)
+ write32(&regs->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_m_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_m_cancel_and_abort(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));
+ qupv3_se_fw_load_and_init(bus, SE_PROTOCOL_SPI, MIXED);
+ clock_enable_qup(bus);
+ m_clk_cfg |= ((div << CLK_DIV_SHFT) | SER_CLK_EN);
+ write32(&regs->geni_ser_m_clk_cfg, m_clk_cfg);
+ /* Mode:0, cpha=0, cpol=0 */
+ write32(&regs->spi_cpha, 0);
+ write32(&regs->spi_cpol, 0);
+
+ /* Serial engine IO initialization */
+ write32(&regs->geni_cgc_ctrl, DEFAULT_CGC_EN);
+ write32(&regs->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(&regs->geni_output_ctrl,
+ DEFAULT_IO_OUTPUT_CTRL_MSK);
+ write32(&regs->geni_force_default_reg, FORCE_DEFAULT);
+
+ /* Serial engine IO set mode */
+ write32(&regs->se_irq_en, (GENI_M_IRQ_EN |
+ GENI_S_IRQ_EN | DMA_TX_IRQ_EN | DMA_RX_IRQ_EN));
+ write32(&regs->se_gsi_event_en, 0);
+
+ /* Set RX and RFR watermark */
+ write32(&regs->geni_rx_watermark_reg, 0);
+ write32(&regs->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(&regs->geni_m_irq_clear, 0xFFFFFFFF);
+ write32(&regs->geni_s_irq_clear, 0xFFFFFFFF);
+ write32(&regs->dma_tx_irq_clr, 0xFFFFFFFF);
+ write32(&regs->dma_rx_irq_clr, 0xFFFFFFFF);
+ write32(&regs->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(&regs->geni_s_irq_enable, (S_COMMON_GENI_S_IRQ_EN
+ | S_CMD_DONE_EN));
+ clrbits32(&regs->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);
+}