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>
This commit is contained in:
parent
7ae833bdaa
commit
47a0832f82
|
@ -6,6 +6,7 @@ bootblock-y += bootblock.c
|
||||||
bootblock-y += mmu.c
|
bootblock-y += mmu.c
|
||||||
bootblock-y += timer.c
|
bootblock-y += timer.c
|
||||||
bootblock-y += spi.c
|
bootblock-y += spi.c
|
||||||
|
bootblock-y += qupv3_spi.c
|
||||||
bootblock-y += gpio.c
|
bootblock-y += gpio.c
|
||||||
bootblock-$(CONFIG_DRIVERS_UART) += uart_bitbang.c
|
bootblock-$(CONFIG_DRIVERS_UART) += uart_bitbang.c
|
||||||
bootblock-y += clock.c
|
bootblock-y += clock.c
|
||||||
|
@ -16,6 +17,7 @@ bootblock-y += qcom_qup_se.c
|
||||||
################################################################################
|
################################################################################
|
||||||
verstage-y += timer.c
|
verstage-y += timer.c
|
||||||
verstage-y += spi.c
|
verstage-y += spi.c
|
||||||
|
verstage-y += qupv3_spi.c
|
||||||
verstage-y += gpio.c
|
verstage-y += gpio.c
|
||||||
verstage-y += clock.c
|
verstage-y += clock.c
|
||||||
verstage-$(CONFIG_SC7180_QSPI) += qspi.c
|
verstage-$(CONFIG_SC7180_QSPI) += qspi.c
|
||||||
|
@ -32,6 +34,7 @@ romstage-y += ../common/mmu.c
|
||||||
romstage-y += mmu.c
|
romstage-y += mmu.c
|
||||||
romstage-y += usb.c
|
romstage-y += usb.c
|
||||||
romstage-y += spi.c
|
romstage-y += spi.c
|
||||||
|
romstage-y += qupv3_spi.c
|
||||||
romstage-y += gpio.c
|
romstage-y += gpio.c
|
||||||
romstage-y += clock.c
|
romstage-y += clock.c
|
||||||
romstage-$(CONFIG_SC7180_QSPI) += qspi.c
|
romstage-$(CONFIG_SC7180_QSPI) += qspi.c
|
||||||
|
@ -43,6 +46,7 @@ romstage-$(CONFIG_DRIVERS_UART) += qupv3_uart.c
|
||||||
ramstage-y += soc.c
|
ramstage-y += soc.c
|
||||||
ramstage-y += timer.c
|
ramstage-y += timer.c
|
||||||
ramstage-y += spi.c
|
ramstage-y += spi.c
|
||||||
|
ramstage-y += qupv3_spi.c
|
||||||
ramstage-y += gpio.c
|
ramstage-y += gpio.c
|
||||||
ramstage-y += clock.c
|
ramstage-y += clock.c
|
||||||
ramstage-$(CONFIG_SC7180_QSPI) += qspi.c
|
ramstage-$(CONFIG_SC7180_QSPI) += qspi.c
|
||||||
|
|
|
@ -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___*/
|
|
@ -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(®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->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->spi_tx_trans_len, size);
|
||||||
|
write32(®s->geni_tx_watermark_reg, TX_WATERMARK);
|
||||||
|
}
|
||||||
|
if (bytes_in)
|
||||||
|
write32(®s->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(®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);
|
||||||
|
|
||||||
|
/* 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));
|
||||||
|
clrbits32(®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);
|
||||||
|
}
|
|
@ -15,6 +15,7 @@
|
||||||
#include <spi-generic.h>
|
#include <spi-generic.h>
|
||||||
#include <spi_flash.h>
|
#include <spi_flash.h>
|
||||||
#include <soc/qspi.h>
|
#include <soc/qspi.h>
|
||||||
|
#include <soc/qupv3_spi.h>
|
||||||
|
|
||||||
static const struct spi_ctrlr qspi_ctrlr = {
|
static const struct spi_ctrlr qspi_ctrlr = {
|
||||||
.claim_bus = sc7180_claim_bus,
|
.claim_bus = sc7180_claim_bus,
|
||||||
|
@ -24,12 +25,24 @@ static const struct spi_ctrlr qspi_ctrlr = {
|
||||||
.max_xfer_size = QSPI_MAX_PACKET_COUNT,
|
.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[] = {
|
const struct spi_ctrlr_buses spi_ctrlr_bus_map[] = {
|
||||||
{
|
{
|
||||||
.ctrlr = &qspi_ctrlr,
|
.ctrlr = &qspi_ctrlr,
|
||||||
.bus_start = CONFIG_BOOT_DEVICE_SPI_FLASH_BUS,
|
.bus_start = CONFIG_BOOT_DEVICE_SPI_FLASH_BUS,
|
||||||
.bus_end = 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);
|
const size_t spi_ctrlr_bus_map_count = ARRAY_SIZE(spi_ctrlr_bus_map);
|
||||||
|
|
Loading…
Reference in New Issue