[PATCH 03/12] spi: Add Qualcomm GENI SPI driver
Stephen Boyd
swboyd at chromium.org
Thu Feb 20 21:58:45 CET 2025
Add a SPI driver for Qualcomm's GENI hardware.
Signed-off-by: Stephen Boyd <swboyd at chromium.org>
---
drivers/spi/Kconfig | 10 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-geni-qcom.c | 527 ++++++++++++++++++++++++++++++++++++
3 files changed, 538 insertions(+)
create mode 100644 drivers/spi/spi-geni-qcom.c
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 96ea033082b5..29e84098cb2f 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -414,6 +414,16 @@ config SPI_QUP
mode supports up to 50MHz, up to four chip selects, programmable
data path from 4 bits to 32 bits and numerous protocol variants.
+config SPI_GENI_QCOM
+ bool "Qualcomm Generic Interface (GENI) SPI controller"
+ depends on ARCH_SNAPDRAGON
+ help
+ Support for the Qualcomm Generic Interface (GENI) SPI controller.
+ The Generic Interface (GENI) is a firmware based Qualcomm Universal
+ Peripherals (QUP) Serial Engine (SE) Wrapper which can support multiple
+ bus protocols depending on the firmware type loaded at early boot time
+ based on system configuration.
+
config RENESAS_RPC_SPI
bool "Renesas RPC SPI driver"
depends on RCAR_64 || RZA1
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 7051e2a00c60..385e5b87d675 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_OMAP3_SPI) += omap3_spi.o
obj-$(CONFIG_PIC32_SPI) += pic32_spi.o
obj-$(CONFIG_PL022_SPI) += pl022_spi.o
obj-$(CONFIG_SPI_QUP) += spi-qup.o
+obj-$(CONFIG_SPI_GENI_QCOM) += spi-geni-qcom.o
obj-$(CONFIG_SPI_MXIC) += spi-mxic.o
obj-$(CONFIG_RENESAS_RPC_SPI) += renesas_rpc_spi.o
obj-$(CONFIG_ROCKCHIP_SFC) += rockchip_sfc.o
diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c
new file mode 100644
index 000000000000..9371f1530c9f
--- /dev/null
+++ b/drivers/spi/spi-geni-qcom.c
@@ -0,0 +1,527 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2017-2018, The Linux foundation. All rights reserved.
+
+#include <asm/io.h>
+#include <clk.h>
+#include <dm.h>
+#include <errno.h>
+#include <linux/iopoll.h>
+#include <spi.h>
+#include <log.h>
+#include <dm/device.h>
+#include <dm/device_compat.h>
+#include <dm/ofnode.h>
+#include <dm/read.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/bitops.h>
+#include <time.h>
+#include <soc/qcom/geni-se.h>
+
+/* SPI SE specific registers and respective register fields */
+#define SE_SPI_CPHA 0x224
+#define CPHA BIT(0)
+
+#define SE_SPI_LOOPBACK 0x22c
+#define LOOPBACK_ENABLE 0x1
+#define NORMAL_MODE 0x0
+#define LOOPBACK_MSK GENMASK(1, 0)
+
+#define SE_SPI_CPOL 0x230
+#define CPOL BIT(2)
+
+#define SE_SPI_DEMUX_OUTPUT_INV 0x24c
+
+#define SE_SPI_DEMUX_SEL 0x250
+
+#define SE_SPI_TRANS_CFG 0x25c
+#define CS_TOGGLE BIT(1)
+
+#define SE_SPI_WORD_LEN 0x268
+#define WORD_LEN_MSK GENMASK(9, 0)
+#define MIN_WORD_LEN 4
+
+#define SE_SPI_TX_TRANS_LEN 0x26c
+#define SE_SPI_RX_TRANS_LEN 0x270
+#define TRANS_LEN_MSK GENMASK(23, 0)
+
+#define SE_SPI_PRE_POST_CMD_DLY 0x274
+
+#define SE_SPI_DELAY_COUNTERS 0x278
+#define SPI_INTER_WORDS_DELAY_MSK GENMASK(9, 0)
+#define SPI_CS_CLK_DELAY_MSK GENMASK(19, 10)
+#define SPI_CS_CLK_DELAY_SHFT 10
+
+#define SE_GENI_M_IRQ_CLEAR 0x618
+#define SE_GENI_M_IRQ_STATUS 0x610
+
+/* M_CMD OP codes for SPI */
+#define SPI_TX_ONLY 1
+#define SPI_RX_ONLY 2
+#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 */
+#define SPI_PRE_CMD_DELAY BIT(0)
+#define TIMESTAMP_BEFORE BIT(1)
+#define FRAGMENTATION BIT(2)
+#define TIMESTAMP_AFTER BIT(3)
+#define POST_CMD_DELAY BIT(4)
+
+#define BYTES_PER_FIFO_WORD 4U
+
+struct geni_spi_priv {
+ fdt_addr_t wrapper;
+ phys_addr_t base;
+ struct clk clk;
+ u32 tx_depth;
+ bool cs_high;
+};
+
+static void geni_se_setup_m_cmd(struct geni_spi_priv *priv, u32 cmd, u32 params)
+{
+ u32 m_cmd;
+
+ debug("%s: cmd=%#x, parms=%#x\n", __func__, cmd, params);
+ m_cmd = (cmd << M_OPCODE_SHFT) | (params & M_PARAMS_MSK);
+ writel(m_cmd, priv->base + SE_GENI_M_CMD0);
+}
+
+static void handle_se_timeout(struct geni_spi_priv *priv)
+{
+ int ret;
+ u32 m_irq;
+
+ writel(0, priv->base + SE_GENI_TX_WATERMARK_REG);
+
+ writel(M_CMD_CANCEL_EN, priv->base + SE_GENI_M_CMD_CTRL_REG);
+
+ ret = readl_poll_timeout(priv->base + SE_GENI_M_IRQ_STATUS, m_irq,
+ (m_irq & M_CMD_CANCEL_EN) == M_CMD_CANCEL_EN,
+ 100);
+ writel(M_CMD_CANCEL_EN, priv->base + SE_GENI_M_IRQ_CLEAR);
+ if (ret < 0) {
+ printf("spi-geni-qcom: Cancel failed. Abort the operation\n");
+
+ writel_relaxed(M_CMD_ABORT_EN, priv->base + SE_GENI_M_CMD_CTRL_REG);
+ ret = readl_poll_timeout(priv->base + SE_GENI_M_IRQ_STATUS, m_irq,
+ (m_irq & M_CMD_ABORT_EN) == M_CMD_ABORT_EN,
+ 100);
+ writel(M_CMD_ABORT_EN, priv->base + SE_GENI_M_IRQ_CLEAR);
+ if (ret < 0)
+ printf("spi-geni-qcom: Abort failed\n");
+ }
+}
+
+static int geni_spi_set_speed(struct udevice *dev, uint speed)
+{
+ /* TODO: Set a clk frequency or change divider here */
+ return 0;
+}
+
+static int geni_spi_set_mode(struct udevice *bus, uint mode)
+{
+ struct geni_spi_priv *priv = dev_get_priv(bus);
+ u32 loopback_cfg = 0, cpol = 0, cpha = 0;
+
+ if (mode & SPI_LOOP)
+ loopback_cfg = LOOPBACK_ENABLE;
+
+ if (mode & SPI_CPOL)
+ cpol = CPOL;
+
+ if (mode & SPI_CPHA)
+ cpha = CPHA;
+
+ if (mode & SPI_CS_HIGH)
+ priv->cs_high = true;
+
+ writel(loopback_cfg, priv->base + SE_SPI_LOOPBACK);
+ writel(cpha, priv->base + SE_SPI_CPHA);
+ writel(cpol, priv->base + SE_SPI_CPOL);
+
+ return 0;
+}
+
+static void geni_spi_reset(struct udevice *dev)
+{
+ struct udevice *bus = dev_get_parent(dev);
+ struct geni_spi_priv *priv = dev_get_priv(bus);
+
+ /* Driver may not be probed yet */
+ if (!priv)
+ return;
+}
+
+#define NUM_PACKING_VECTORS 4
+#define PACKING_START_SHIFT 5
+#define PACKING_DIR_SHIFT 4
+#define PACKING_LEN_SHIFT 1
+#define PACKING_STOP_BIT BIT(0)
+#define PACKING_VECTOR_SHIFT 10
+static void geni_spi_config_packing(struct geni_spi_priv *geni, int bpw,
+ int pack_words, bool msb_to_lsb,
+ bool tx_cfg, bool rx_cfg)
+{
+ u32 cfg0, cfg1, cfg[NUM_PACKING_VECTORS] = {0};
+ int len;
+ int temp_bpw = bpw;
+ int idx_start = msb_to_lsb ? bpw - 1 : 0;
+ int idx = idx_start;
+ int idx_delta = msb_to_lsb ? -BITS_PER_BYTE : BITS_PER_BYTE;
+ int ceil_bpw = ALIGN(bpw, BITS_PER_BYTE);
+ int iter = (ceil_bpw * pack_words) / BITS_PER_BYTE;
+ int i;
+
+ if (iter <= 0 || iter > NUM_PACKING_VECTORS)
+ return;
+
+ for (i = 0; i < iter; i++) {
+ len = min_t(int, temp_bpw, BITS_PER_BYTE) - 1;
+ cfg[i] = idx << PACKING_START_SHIFT;
+ cfg[i] |= msb_to_lsb << PACKING_DIR_SHIFT;
+ cfg[i] |= len << PACKING_LEN_SHIFT;
+
+ if (temp_bpw <= BITS_PER_BYTE) {
+ idx = ((i + 1) * BITS_PER_BYTE) + idx_start;
+ temp_bpw = bpw;
+ } else {
+ idx = idx + idx_delta;
+ temp_bpw = temp_bpw - BITS_PER_BYTE;
+ }
+ }
+ cfg[iter - 1] |= PACKING_STOP_BIT;
+ cfg0 = cfg[0] | (cfg[1] << PACKING_VECTOR_SHIFT);
+ cfg1 = cfg[2] | (cfg[3] << PACKING_VECTOR_SHIFT);
+
+ if (tx_cfg) {
+ writel(cfg0, geni->base + SE_GENI_TX_PACKING_CFG0);
+ writel(cfg1, geni->base + SE_GENI_TX_PACKING_CFG1);
+ }
+ if (rx_cfg) {
+ writel(cfg0, geni->base + SE_GENI_RX_PACKING_CFG0);
+ writel(cfg1, geni->base + SE_GENI_RX_PACKING_CFG1);
+ }
+
+ /*
+ * Number of protocol words in each FIFO entry
+ * 0 - 4x8, four words in each entry, max word size of 8 bits
+ * 1 - 2x16, two words in each entry, max word size of 16 bits
+ * 2 - 1x32, one word in each entry, max word size of 32 bits
+ * 3 - undefined
+ */
+ if (pack_words || bpw == 32)
+ writel(bpw / 16, geni->base + SE_GENI_BYTE_GRAN);
+}
+
+static u32 geni_spi_get_tx_fifo_depth(struct geni_spi_priv *geni)
+{
+ u32 val, hw_version, hw_major, hw_minor, tx_fifo_depth_mask;
+
+ hw_version = readl(geni->wrapper + QUP_HW_VER_REG);
+ hw_major = GENI_SE_VERSION_MAJOR(hw_version);
+ hw_minor = GENI_SE_VERSION_MINOR(hw_version);
+
+ if ((hw_major == 3 && hw_minor >= 10) || hw_major > 3)
+ tx_fifo_depth_mask = TX_FIFO_DEPTH_MSK_256_BYTES;
+ else
+ tx_fifo_depth_mask = TX_FIFO_DEPTH_MSK;
+
+ val = readl(geni->base + SE_HW_PARAM_0);
+
+ return (val & tx_fifo_depth_mask) >> TX_FIFO_DEPTH_SHFT;
+}
+
+static void geni_spi_drain_rx(struct geni_spi_priv *geni)
+{
+ u32 rx_fifo_status;
+ unsigned int rx_bytes;
+ unsigned int rx_last_byte_valid;
+ unsigned int i;
+
+ rx_fifo_status = readl(geni->base + SE_GENI_RX_FIFO_STATUS);
+ rx_bytes = (rx_fifo_status & RX_FIFO_WC_MSK) * BYTES_PER_FIFO_WORD;
+ if (rx_fifo_status & RX_LAST) {
+ rx_last_byte_valid = rx_fifo_status & RX_LAST_BYTE_VALID_MSK;
+ rx_last_byte_valid >>= RX_LAST_BYTE_VALID_SHFT;
+ if (rx_last_byte_valid && rx_last_byte_valid < 4)
+ rx_bytes -= BYTES_PER_FIFO_WORD - rx_last_byte_valid;
+ }
+
+ for (i = 0; i < DIV_ROUND_UP(rx_bytes, BYTES_PER_FIFO_WORD); i++)
+ readl(geni->base + SE_GENI_RX_FIFOn);
+}
+
+static void geni_spi_hw_init(struct udevice *dev)
+{
+ struct udevice *bus = dev_get_parent(dev);
+ struct geni_spi_priv *geni = dev_get_priv(bus);
+ u32 demux_output_inv = 0;
+ u32 val, demux_sel;
+
+ writel(0, geni->base + SE_GSI_EVENT_EN);
+ writel(0xffffffff, geni->base + SE_GENI_M_IRQ_CLEAR);
+ writel(0xffffffff, geni->base + SE_GENI_S_IRQ_CLEAR);
+ writel(0xffffffff, geni->base + SE_IRQ_EN);
+
+ val = readl(geni->base + GENI_CGC_CTRL);
+ val |= DEFAULT_CGC_EN;
+ writel(val, geni->base + GENI_CGC_CTRL);
+
+ writel(DEFAULT_IO_OUTPUT_CTRL_MSK, geni->base + GENI_OUTPUT_CTRL);
+ writel(FORCE_DEFAULT, geni->base + GENI_FORCE_DEFAULT_REG);
+
+ val = readl(geni->base + SE_IRQ_EN);
+ val |= GENI_M_IRQ_EN | GENI_S_IRQ_EN;
+ writel(val, geni->base + SE_IRQ_EN);
+
+ val = readl(geni->base + SE_GENI_DMA_MODE_EN);
+ val &= ~GENI_DMA_MODE_EN;
+ writel(val, geni->base + SE_GENI_DMA_MODE_EN);
+
+ writel(0, geni->base + SE_GSI_EVENT_EN);
+
+ writel(geni->tx_depth - 3, geni->base + SE_GENI_RX_WATERMARK_REG);
+ writel(geni->tx_depth - 2, geni->base + SE_GENI_RX_RFR_WATERMARK_REG);
+
+ val = readl(geni->base + SE_GENI_M_IRQ_EN);
+ val |= M_COMMON_GENI_M_IRQ_EN;
+ val |= M_CMD_DONE_EN | M_TX_FIFO_WATERMARK_EN;
+ val |= M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN;
+ writel(val, geni->base + SE_GENI_M_IRQ_EN);
+
+ val = readl(geni->base + SE_GENI_S_IRQ_EN);
+ val |= S_COMMON_GENI_S_IRQ_EN;
+ writel(val, geni->base + SE_GENI_S_IRQ_EN);
+
+ if (geni->cs_high)
+ demux_output_inv = BIT(spi_chip_select(dev));
+ demux_sel = spi_chip_select(dev);
+ writel(demux_sel, geni->base + SE_SPI_DEMUX_SEL);
+ writel(demux_output_inv, geni->base + SE_SPI_DEMUX_OUTPUT_INV);
+
+ writel(((8 - MIN_WORD_LEN) & WORD_LEN_MSK), geni->base + SE_SPI_WORD_LEN);
+ geni_spi_config_packing(geni, BITS_PER_BYTE, 4, true, true, true);
+
+ geni_spi_drain_rx(geni);
+}
+
+static int geni_spi_claim_bus(struct udevice *dev)
+{
+ geni_spi_hw_init(dev);
+ return 0;
+}
+
+static int geni_spi_release_bus(struct udevice *dev)
+{
+ /* Reset the SPI hardware */
+ geni_spi_reset(dev);
+
+ return 0;
+}
+
+static int geni_spi_set_cs(struct udevice *bus, bool enable)
+{
+ struct geni_spi_priv *priv = dev_get_priv(bus);
+ u32 m_cmd = 0, m_irq;
+ int ret;
+
+ debug("%s: enable=%d\n", __func__, enable);
+
+ m_cmd = enable ? SPI_CS_ASSERT : SPI_CS_DEASSERT;
+ geni_se_setup_m_cmd(priv, m_cmd, 0);
+
+ ret = readl_poll_timeout(priv->base + SE_GENI_M_IRQ_STATUS, m_irq,
+ (m_irq & M_CMD_DONE_EN) == M_CMD_DONE_EN,
+ 100);
+ writel(M_CMD_DONE_EN, priv->base + SE_GENI_M_IRQ_CLEAR);
+ if (ret) {
+ printf("spi-geni-qcom: Timeout setting cs\n");
+ handle_se_timeout(priv);
+ return ret;
+ }
+
+ return 0;
+}
+
+static unsigned int
+geni_spi_handle_tx(struct geni_spi_priv *geni, const u8 *dout, unsigned int tx_rem_bytes)
+{
+ unsigned int max_bytes;
+ unsigned int i = 0;
+
+ max_bytes = (geni->tx_depth - 1) * BYTES_PER_FIFO_WORD;
+ if (tx_rem_bytes < max_bytes)
+ max_bytes = tx_rem_bytes;
+
+ while (i < max_bytes) {
+ unsigned int j;
+ unsigned int bytes_to_write;
+ u32 fifo_word = 0;
+ u8 *fifo_byte = (u8 *)&fifo_word;
+
+ bytes_to_write = min(BYTES_PER_FIFO_WORD, max_bytes - i);
+ for (j = 0; j < bytes_to_write; j++)
+ fifo_byte[j] = dout[i++];
+ iowrite32_rep((void *)(geni->base + SE_GENI_TX_FIFOn), &fifo_word, 1);
+ }
+ tx_rem_bytes -= max_bytes;
+ if (!tx_rem_bytes)
+ writel(0, geni->base + SE_GENI_TX_WATERMARK_REG);
+
+ return max_bytes;
+}
+
+static int geni_spi_handle_rx(struct geni_spi_priv *geni, u8 *din, unsigned int rx_rem_bytes)
+{
+ u32 rx_fifo_status;
+ unsigned int rx_bytes;
+ unsigned int rx_last_byte_valid;
+ unsigned int i = 0;
+
+ rx_fifo_status = readl(geni->base + SE_GENI_RX_FIFO_STATUS);
+ rx_bytes = (rx_fifo_status & RX_FIFO_WC_MSK) * BYTES_PER_FIFO_WORD;
+ if (rx_fifo_status & RX_LAST) {
+ rx_last_byte_valid = rx_fifo_status & RX_LAST_BYTE_VALID_MSK;
+ rx_last_byte_valid >>= RX_LAST_BYTE_VALID_SHFT;
+ if (rx_last_byte_valid && rx_last_byte_valid < 4)
+ rx_bytes -= BYTES_PER_FIFO_WORD - rx_last_byte_valid;
+ }
+
+ if (rx_rem_bytes < rx_bytes)
+ rx_bytes = rx_rem_bytes;
+
+ while (i < rx_bytes) {
+ u32 fifo_word = 0;
+ u8 *fifo_byte = (u8 *)&fifo_word;
+ unsigned int bytes_to_read;
+ unsigned int j;
+
+ bytes_to_read = min(BYTES_PER_FIFO_WORD, rx_bytes - i);
+ ioread32_rep((void *)(geni->base + SE_GENI_RX_FIFOn), &fifo_word, 1);
+ for (j = 0; j < bytes_to_read; j++)
+ din[i++] = fifo_byte[j];
+ }
+
+ return rx_bytes;
+}
+
+static int 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 geni_spi_priv *priv = dev_get_priv(bus);
+ unsigned int len = bitlen >> 3;
+ unsigned int rx_rem_bytes = din ? len : 0;
+ unsigned int tx_rem_bytes = dout ? len : 0;
+ int ret = 0;
+ u32 m_cmd = 0, m_irq;
+ ulong start;
+
+ if (len & ~TRANS_LEN_MSK) {
+ printf("spi-geni-qcom: transfer length too long (%d)\n", len);
+ return -EINVAL;
+ }
+
+ if (flags & SPI_XFER_BEGIN) {
+ geni_spi_hw_init(dev);
+ ret = geni_spi_set_cs(bus, true);
+ if (ret != 0)
+ return ret;
+ }
+
+ if (len) {
+ if (din) {
+ m_cmd |= SPI_RX_ONLY;
+ writel(len, priv->base + SE_SPI_RX_TRANS_LEN);
+ }
+ if (dout) {
+ m_cmd |= SPI_TX_ONLY;
+ writel(len, priv->base + SE_SPI_TX_TRANS_LEN);
+ writel(1, priv->base + SE_GENI_TX_WATERMARK_REG);
+ }
+
+ geni_se_setup_m_cmd(priv, m_cmd, FRAGMENTATION);
+
+ start = get_timer(0);
+ do {
+ ret = readl_poll_timeout(priv->base + SE_GENI_M_IRQ_STATUS, m_irq,
+ m_irq != 0, 1000);
+ if ((m_irq & M_RX_FIFO_WATERMARK_EN) || (m_irq & M_RX_FIFO_LAST_EN)) {
+ rx_rem_bytes -= geni_spi_handle_rx(priv, din + len - rx_rem_bytes,
+ rx_rem_bytes);
+ }
+ if (m_irq & M_TX_FIFO_WATERMARK_EN) {
+ tx_rem_bytes -= geni_spi_handle_tx(priv, dout + len - tx_rem_bytes,
+ tx_rem_bytes);
+ }
+ writel(m_irq, priv->base + SE_GENI_M_IRQ_CLEAR);
+ if (m_irq & M_CMD_DONE_EN)
+ break;
+ } while (get_timer(start) < 100000);
+
+ if (!(m_irq & M_CMD_DONE_EN) || tx_rem_bytes || rx_rem_bytes) {
+ printf("spi-geni-qcom: Transfer failed\n");
+ handle_se_timeout(priv);
+ return -ETIMEDOUT;
+ }
+ }
+
+ if (flags & SPI_XFER_END) {
+ ret = geni_spi_set_cs(bus, false);
+ if (ret != 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+static const struct dm_spi_ops geni_spi_ops = {
+ .claim_bus = geni_spi_claim_bus,
+ .release_bus = geni_spi_release_bus,
+ .xfer = geni_spi_xfer,
+ .set_speed = geni_spi_set_speed,
+ .set_mode = geni_spi_set_mode,
+};
+
+static int geni_spi_probe(struct udevice *dev)
+{
+ ofnode parent_node = ofnode_get_parent(dev_ofnode(dev));
+ struct geni_spi_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ priv->base = dev_read_addr(dev);
+ if (priv->base == FDT_ADDR_T_NONE)
+ return -EINVAL;
+
+ priv->wrapper = ofnode_get_addr(parent_node);
+ if (priv->wrapper == FDT_ADDR_T_NONE)
+ return -EINVAL;
+
+ ret = clk_get_by_index(dev, 0, &priv->clk);
+ if (ret)
+ return ret;
+
+ ret = clk_enable(&priv->clk);
+ if (ret < 0)
+ return ret;
+
+ priv->tx_depth = geni_spi_get_tx_fifo_depth(priv);
+
+ return 0;
+}
+
+static const struct udevice_id spi_geni_ids[] = {
+ { .compatible = "qcom,geni-spi" },
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(geni_spi) = {
+ .name = "geni_spi",
+ .id = UCLASS_SPI,
+ .of_match = spi_geni_ids,
+ .ops = &geni_spi_ops,
+ .priv_auto = sizeof(struct geni_spi_priv),
+ .probe = geni_spi_probe,
+};
--
Sent by a computer, using git, on the internet
More information about the U-Boot
mailing list