+ if (!rx_bounce)
+ return -ENOMEM;
+ rx_buf = rx_bounce;
+ }
+
+ writel(dout ? len : 0, priv->base + SE_SPI_TX_TRANS_LEN);
+ writel(din ? len : 0, priv->base + SE_SPI_RX_TRANS_LEN);
+
+ /* SPI_TX_ONLY | SPI_RX_ONLY is not a valid opcode, use SPI_TX_RX
instead */
+ if (dout && din)
+ cmd = SPI_TX_RX;
+ else if (din)
+ cmd = SPI_RX_ONLY;
+ else if (dout)
+ cmd = SPI_TX_ONLY;
+ else
+ cmd = 0;
+
+ if (!xfer_end)
+ m_param |= FRAGMENTATION;
+
+ if (use_dma) {
+ setbits_le32(priv->base + SE_GENI_DMA_MODE_EN,
GENI_DMA_MODE_EN);
+
+ /* DMA: issue M_CMD0 first, then arm DMA descriptors */
+ writel((cmd << M_OPCODE_SHFT) | (m_param & M_PARAMS_MSK),
+ priv->base + SE_GENI_M_CMD0);
+ if (din)
+ qcom_geni_spi_dma_rx_start(priv, rx_buf, len);
+ if (dout)
+ qcom_geni_spi_dma_tx_start(priv, dout, len);
+
+ ret = qcom_geni_spi_dma_xfer_wait(priv, dout, rx_buf, len,
timeout_ms);
+
+ /* Clear M_CMD_DONE so status doesn't accumulate across
transfers */
+ writel(readl(priv->base + SE_GENI_M_IRQ_STATUS),
+ priv->base + SE_GENI_M_IRQ_CLEAR);
+ } else {
+ clrbits_le32(priv->base + SE_GENI_DMA_MODE_EN,
GENI_DMA_MODE_EN);
+
+ /*
+ * Set watermarks before M_CMD0. Only set TX_WATERMARK when
+ * we have TX data, since SPI_RX_ONLY doesn't use the TX FIFO.
+ */
+ if (dout)
+ writel(1, priv->base + SE_GENI_TX_WATERMARK_REG);
+ /* RX_WATERMARK=0: SE fires RX_FIFO_LAST at end of transfer */
+ if (din)
+ writel(0, priv->base + SE_GENI_RX_WATERMARK_REG);
+
+ writel((cmd << M_OPCODE_SHFT) | (m_param & M_PARAMS_MSK),
+ priv->base + SE_GENI_M_CMD0);
+
+ ret = qcom_geni_spi_fifo_xfer(priv, dout, din, len, timeout_ms);
+ }
+
+ if (rx_bounce) {
+ if (!ret)
+ memcpy(din, rx_bounce, len);
+ free(rx_bounce);
+ }
+
+ if (ret) {
+ int abort_ret = qcom_geni_spi_abort(dev, priv);
+
+ if (abort_ret)
+ dev_err(dev, "abort failed: %d\n", abort_ret);
+ }
+
+ return ret;
+}
+
+static int qcom_geni_spi_xfer(struct udevice *dev, unsigned int bitlen,
+ const void *dout, void *din, unsigned long flags)
+{
+ struct udevice *bus = dev_get_parent(dev);
+ struct qcom_geni_spi_priv *priv = dev_get_priv(bus);
+ unsigned int len = DIV_ROUND_UP(bitlen, 8);
+ unsigned int done = 0;
+ int ret;
+
+ if (flags & SPI_XFER_BEGIN)
+ writel(0xffffffff, priv->base + SE_GENI_M_IRQ_CLEAR);
+
+ while (done < len) {
+ unsigned int chunk = min_t(unsigned int, len - done,
+ SPI_GENI_MAX_XFER_BYTES);
+ bool xfer_end = (flags & SPI_XFER_END) && (done + chunk == len);
+
+ ret = qcom_geni_spi_xfer_once(dev,
+ chunk,
+ dout ? (const u8 *)dout + done : NULL,
+ din ? (u8 *)din + done : NULL,
+ xfer_end);
+ if (ret)
+ return ret;
+
+ done += chunk;
+ }
+
+ return 0;
+}
+
+static int qcom_geni_spi_set_speed(struct udevice *bus, uint speed)
+{
+ struct qcom_geni_spi_priv *priv = dev_get_priv(bus);
+ ulong parent_rate;
+ u32 div;
+
+ if (!speed)
+ return -EINVAL;
+
+ parent_rate = clk_get_rate(&priv->se);
+ if (IS_ERR_VALUE(parent_rate) || !parent_rate)
+ div = priv->oversampling;
+ else
+ div = DIV_ROUND_UP(parent_rate, priv->oversampling * speed);
+
+ div = clamp_t(u32, div, 1, CLK_DIV_MSK >> CLK_DIV_SHFT);
+
+ writel(0, priv->base + SE_GENI_CLK_SEL);
+ writel((div << CLK_DIV_SHFT) | SER_CLK_EN, priv->base +
GENI_SER_M_CLK_CFG);
+
+ return 0;
+}
+
+static int qcom_geni_spi_set_mode(struct udevice *bus, uint mode)
+{
+ struct qcom_geni_spi_priv *priv = dev_get_priv(bus);
+ u32 val;
+
+ val = readl(priv->base + SE_SPI_LOOPBACK);
+ val &= ~LOOPBACK_MSK;
+ if (mode & SPI_LOOP)
+ val |= LOOPBACK_ENABLE;
+ writel(val, priv->base + SE_SPI_LOOPBACK);
+
+ val = readl(priv->base + SE_SPI_CPHA);
+ if (mode & SPI_CPHA)
+ val |= CPHA;
+ else
+ val &= ~CPHA;
+ writel(val, priv->base + SE_SPI_CPHA);
+
+ val = readl(priv->base + SE_SPI_CPOL);
+ if (mode & SPI_CPOL)
+ val |= CPOL;
+ else
+ val &= ~CPOL;
+ writel(val, priv->base + SE_SPI_CPOL);
+
+ return 0;
+}
+
+static int qcom_geni_spi_claim_bus(struct udevice *dev)
+{
+ struct udevice *bus = dev_get_parent(dev);
+ struct qcom_geni_spi_priv *priv = dev_get_priv(bus);
+ struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
+ unsigned int cs = slave_plat->cs[0];
+ unsigned int bpw = slave_plat->wordlen ? slave_plat->wordlen :
SPI_WORD_LEN_BITS;
+
+ priv->bpw = bpw;
+ priv->bytes_per_fifo_word = qcom_geni_spi_bytes_per_fifo_word(bpw);
+
+ writel((bpw - MIN_WORD_LEN) & WORD_LEN_MSK, priv->base +
SE_SPI_WORD_LEN);
+ writel(cs, priv->base + SE_SPI_DEMUX_SEL);
+ writel(slave_plat->mode & SPI_CS_HIGH ? BIT(cs) : 0,
+ priv->base + SE_SPI_DEMUX_OUTPUT_INV);
+
+ qcom_geni_spi_config_packing(priv, bpw, true);
+
+ writel(0xffffffff, priv->base + SE_GENI_M_IRQ_CLEAR);
+
+ return 0;
+}
+
+static int qcom_geni_spi_release_bus(struct udevice *dev)
+{
+ return 0;
+}
+
+static u32 qcom_geni_spi_get_tx_fifo_depth(struct qcom_geni_spi_priv *priv)
+{
+ u32 val, hw_version, depth_mask;
+
+ hw_version = readl(priv->wrapper + QUP_HW_VER_REG);
+ depth_mask = geni_se_fifo_depth_mask(hw_version,
TX_FIFO_DEPTH_MSK_256_BYTES,
+ TX_FIFO_DEPTH_MSK);
+
+ val = readl(priv->base + SE_HW_PARAM_0);
+
+ return (val & depth_mask) >> TX_FIFO_DEPTH_SHFT;
+}
+
+/* QUP v1.0 undersamples the SPI clock and needs 2x the requested bit rate */
+static u32 qcom_geni_spi_get_oversampling(struct qcom_geni_spi_priv *priv)
+{
+ u32 hw_version = readl(priv->wrapper + QUP_HW_VER_REG);
+ u32 hw_major = GENI_SE_VERSION_MAJOR(hw_version);
+ u32 hw_minor = GENI_SE_VERSION_MINOR(hw_version);
+
+ if (hw_major == 1 && hw_minor == 0)
+ return 2;
+
+ return 1;
+}
+
+static void qcom_geni_spi_hw_init(struct qcom_geni_spi_priv *priv)
+{
+ u32 val;
+
+ writel(0xffffffff, priv->base + SE_GENI_M_IRQ_CLEAR);
+
+ val = readl(priv->base + GENI_CGC_CTRL);
+ val |= DEFAULT_CGC_EN;
+ writel(val, priv->base + GENI_CGC_CTRL);
+
+ writel(DEFAULT_IO_OUTPUT_CTRL_MSK, priv->base + GENI_OUTPUT_CTRL);
+ writel(FORCE_DEFAULT, priv->base + GENI_FORCE_DEFAULT_REG);
+
+ val = readl(priv->base + SE_IRQ_EN);
+ val |= GENI_M_IRQ_EN;
+ writel(val, priv->base + SE_IRQ_EN);
+
+ writel(priv->tx_wm, priv->base + SE_GENI_TX_WATERMARK_REG);
+
+ val = readl(priv->base + SE_GENI_M_IRQ_EN);
+ val |= M_COMMON_GENI_M_IRQ_EN | M_CMD_DONE_EN | SPI_ERR;
+ writel(val, priv->base + SE_GENI_M_IRQ_EN);
+
+ writel(0xffffffff, priv->base + SE_DMA_TX_IRQ_CLR);
+ writel(0xffffffff, priv->base + SE_DMA_RX_IRQ_CLR);
+ writel(TX_DMA_DONE | TX_SBE, priv->base + SE_DMA_TX_IRQ_EN_SET);
+ writel(RX_DMA_DONE | RX_SBE, priv->base + SE_DMA_RX_IRQ_EN_SET);
+
+ /* We always control CS manually, don't let the SE auto-toggle it */
+ val = readl(priv->base + SE_SPI_TRANS_CFG);
+ val &= ~CS_TOGGLE;
+ writel(val, priv->base + SE_SPI_TRANS_CFG);
+}
+
+static int qcom_geni_spi_probe(struct udevice *dev)
+{
+ struct qcom_geni_spi_priv *priv = dev_get_priv(dev);
+ u32 proto;
+ int ret;
+
+ priv->wrapper = dev_read_addr(dev->parent);
+ if (priv->wrapper == FDT_ADDR_T_NONE)
+ return -EINVAL;
+
+ priv->base = dev_read_addr(dev);
+ if (priv->base == FDT_ADDR_T_NONE)
+ return -EINVAL;
+
+ ret = clk_get_by_name(dev, "se", &priv->se);
+ if (ret) {
+ dev_err(dev, "clk_get_by_name(se) failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_enable(&priv->se);
+ if (ret) {
+ dev_err(dev, "clk_enable(se) failed: %d\n", ret);
+ return ret;
+ }
+
+ proto = readl(priv->base + GENI_FW_REVISION_RO);
+ proto &= FW_REV_PROTOCOL_MSK;
+ proto >>= FW_REV_PROTOCOL_SHFT;
+
+ if (proto == GENI_SE_INVALID_PROTO) {
+ dev_info(dev, "firmware not loaded, loading now\n");
+ if (ret) {
+ dev_err(dev, "firmware load failed: %d\n", ret);
+ clk_disable(&priv->se);
+ return ret;
+ }
+ proto = readl(priv->base + GENI_FW_REVISION_RO);
+ proto &= FW_REV_PROTOCOL_MSK;
+ proto >>= FW_REV_PROTOCOL_SHFT;
+ dev_info(dev, "firmware loaded, proto=0x%x\n", proto);
+ } else {
+ dev_info(dev, "firmware already loaded, proto=0x%x\n", proto);
+ }
+
+ if (proto != GENI_SE_SPI) {
+ dev_err(dev, "Invalid proto %d\n", proto);
+ clk_disable(&priv->se);
+ return -ENXIO;
+ }
+
+ /*
+ * SE-DMA is an inherent capability of the GENI SE core (mirrors
+ * upstream Linux's spi-geni-qcom.c, which never gates SE-DMA by DT
+ * or hardware version). Only FIFO availability needs checking here.
+ */
+ priv->fifo_capable = !(readl(priv->base + GENI_IF_DISABLE_RO) &
FIFO_IF_DISABLE);
+
+ priv->tx_fifo_depth = qcom_geni_spi_get_tx_fifo_depth(priv);
+ if (!priv->tx_fifo_depth) {
+ dev_err(dev, "Invalid TX FIFO depth\n");
+ clk_disable(&priv->se);
+ return -ENXIO;
+ }
+ priv->tx_wm = priv->tx_fifo_depth - 1;
+ priv->oversampling = qcom_geni_spi_get_oversampling(priv);
+
+ qcom_geni_spi_hw_init(priv);
+
+ return 0;
+}
+
+static const struct dm_spi_ops qcom_geni_spi_ops = {
+ .claim_bus = qcom_geni_spi_claim_bus,
+ .release_bus = qcom_geni_spi_release_bus,
+ .xfer = qcom_geni_spi_xfer,
+ .set_speed = qcom_geni_spi_set_speed,
+ .set_mode = qcom_geni_spi_set_mode,
+ /*
+ * cs_info is not needed, since we require all chip selects to be
+ * in the device tree explicitly
+ */
+};
+
+static const struct udevice_id qcom_geni_spi_ids[] = {
+ { .compatible = "qcom,geni-spi" },
+ { }
+};
+
+U_BOOT_DRIVER(qcom_geni_spi) = {
+ .name = "qcom_geni_spi",
+ .id = UCLASS_SPI,
+ .of_match = qcom_geni_spi_ids,
+ .probe = qcom_geni_spi_probe,
+ .priv_auto = sizeof(struct qcom_geni_spi_priv),
+ .ops = &qcom_geni_spi_ops,
+};
diff --git a/include/soc/qcom/geni-se.h b/include/soc/qcom/geni-se.h
index fc9a8e82cd8..79516c1f846 100644
--- a/include/soc/qcom/geni-se.h
+++ b/include/soc/qcom/geni-se.h
@@ -68,11 +68,19 @@ enum geni_se_protocol_type {
#define SE_DMA_TX_IRQ_CLR 0xc44
#define SE_DMA_TX_IRQ_EN_SET 0xc4c
#define SE_DMA_TX_FSM_RST 0xc58
+#define SE_DMA_TX_PTR_L 0xc30
+#define SE_DMA_TX_PTR_H 0xc34
+#define SE_DMA_TX_ATTR 0xc38
+#define SE_DMA_TX_LEN 0xc3c
#define SE_DMA_RX_IRQ_STAT 0xd40
#define SE_DMA_RX_IRQ_CLR 0xd44
#define SE_DMA_RX_IRQ_EN_SET 0xd4c
#define SE_DMA_RX_LEN_IN 0xd54
#define SE_DMA_RX_FSM_RST 0xd58
+#define SE_DMA_RX_PTR_L 0xd30
+#define SE_DMA_RX_PTR_H 0xd34
+#define SE_DMA_RX_ATTR 0xd38
+#define SE_DMA_RX_LEN 0xd3c
#define SE_GSI_EVENT_EN 0xe18
#define SE_IRQ_EN 0xe1c
#define SE_HW_PARAM_0 0xe24
@@ -277,6 +285,30 @@ enum geni_se_protocol_type {
#define GENI_SE_VERSION_MINOR(ver) ((ver & HW_VER_MINOR_MASK) >>
HW_VER_MINOR_SHFT)
#define GENI_SE_VERSION_STEP(ver) (ver & HW_VER_STEP_MASK)
+/*
+ * geni_se_fifo_depth_mask() - Pick the TX/RX SE_HW_PARAM_x fifo depth mask
+ * for a given QUP_HW_VER_REG value.
+ * @hw_version: value read from QUP_HW_VER_REG
+ * @depth_mask_256: mask to use on HW that supports 256-byte-deep fifos
+ * (QUP HW version >= 3.10, 8-bit depth field)
+ * @depth_mask: mask to use on older HW (6-bit depth field)
+ *
+ * QUP HW version >= 3.10 widened the fifo depth field in SE_HW_PARAM_0
+ * (TX) and SE_HW_PARAM_1 (RX) from 6 bits to 8 bits; both fields are
+ * gated by the same major/minor check.
+ */
+static inline u32 geni_se_fifo_depth_mask(u32 hw_version, u32 depth_mask_256,
+ u32 depth_mask)
+{
+ u32 hw_major = GENI_SE_VERSION_MAJOR(hw_version);
+ u32 hw_minor = GENI_SE_VERSION_MINOR(hw_version);
+
+ if ((hw_major == 3 && hw_minor >= 10) || hw_major > 3)
+ return depth_mask_256;
+
+ return depth_mask;
+}
+
/* QUP SE VERSION value for major number 2 and minor number 5 */
#define QUP_SE_VERSION_2_5 0x20050000
---
base-commit: ece349ade2973e220f524ce59e59711cc919263f
change-id: 20260826-geni-spi-v2-review-20f0279ee7c0
Best regards,
--
Vandhiadevan Karunamoorthy <[email protected]>