From nobody Thu Apr 2 17:16:00 2026 Received: from mail-wm1-f41.google.com (mail-wm1-f41.google.com [209.85.128.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 987E138F923 for ; Fri, 27 Mar 2026 11:48:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774612138; cv=none; b=mRbg/fpyMQ62kLZyPeHOreJPUdipamDitRZuuXGKpOXzNoRo6/V5qQU90tHJc+F+L4NOKV8Afax+FbIOXVhGzIPdWo1VH/8LrH8LBlcUIsZgMvPZlrCdmfGdsZpdW8Rrk3e+ua1O1i5Fq90DElJXvcOEKZ8V2f82CZxaKBST5+M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774612138; c=relaxed/simple; bh=KVNBLzsJSlo69J81bVVT04FUHX00uKtaPw+6RZMH99o=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=jmKTOLIhwzApRbEpAsVkqaGMHuxdTdWp2jodaIoAdyF6p8vvWcAlfq+43upTYqnTeUEQ/6uqfLDvk2o+LNEzcAJrLpX8MTcFkn/0xgMGBs1goKVBWuoE8MVGQfRHUulMUr7IRqOt2hUTYbA5uLktPiaqiwJ7mTRr55dz1ONvjP0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=DXA7dLMR; arc=none smtp.client-ip=209.85.128.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="DXA7dLMR" Received: by mail-wm1-f41.google.com with SMTP id 5b1f17b1804b1-486fb439299so19108595e9.0 for ; Fri, 27 Mar 2026 04:48:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1774612135; x=1775216935; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=JFBGm0691jNrch3w/l7G28yS+4rdPlF/SBkrUrWasg8=; b=DXA7dLMR1k6tw3d1/z1uBZ1oQ9tRXkDwMtpkoJWSC8PsLSV4qVuzw9AMFI00kZ8pmk /U1lq8Lc68Webbdup3Tazy6byCUtScb7lUOMQV4pvI2P/7i6VDiNboRTSzf8888D5gEC A2+SsgtDBbDpvTIVFPCGTzJlQmvAcMGlFZGOXC9vryTix06w+5pVweAIpQjIE9L+FYp4 SiwFS0hgMrn2H+6TT09jz17HeVE75PGrHC72pSy0KSrtoaxJB/I05FzbpuHa5Sa2lLXW YqJg2xnpRT+nM1c1iDZA+egAHTZwLvz7o/JOauQSyw4BXyU7DR/XuqbQmaql+mY7r/NX sCVQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774612135; x=1775216935; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=JFBGm0691jNrch3w/l7G28yS+4rdPlF/SBkrUrWasg8=; b=RsCqA9/X3uFnGxfRgy0TDKBz4LzLMzW53tMeZd7iX1J8ur/dsa1O7c4kfE8IUuDz6u n8G5jE3f2mN3XJNjNOudmi1lkWnSCJRkg3J+zhiBwaXVaAz7R5JO4UDw5Wt1gRGf+dRu 8JF/TuHUgfvSqKUOT7O3PfPla/On2uQqLMnVifyW7rpgsLt3P2S7OdGzpafn/4yEc8lu rsovsZOtXWmXB7LqexbFiTLdwvWR+5QNvcZAuzrC0fArfUXd591cGhHIuSQ6JrMRdXfg SccXCYF4zzV+qwc6JXxdypigrVWobma5LkTu6zUUDos84YdVupSZNA6yySE+8sB3Knox 3Ifw== X-Forwarded-Encrypted: i=1; AJvYcCUvPERjC7obyDJ0U1coxm9iUl1Jlp/kp/o3Rtpw/sIj2K8Qr0NFeC7Wa5zZEoIJjnTWBqxAT6OV7BFhHlw=@vger.kernel.org X-Gm-Message-State: AOJu0Yx2tCcR60l0tbXBVizMO631Aclf+fXj30X/P/coHENFwhfXuH2E GIakaHjzZXT7zQ/i67BvTh8MrXZz9zQVPOOv0ssfSc7xgog3GXhdbiFS X-Gm-Gg: ATEYQzxS0prZLybDCzdtfbkzHY8kstE3EdyHUNufcYN/euX+iL1czYsgVKaU5g2TFOV 1t+wgYu0NH5t8v9RBiFERvn0YMgLaEHrJVQdK4SEKSN5KY7josgXGbfyYwK9knP8ng7cKIUtaML AbmT2249iULXjprqEr9gzKKaUUtprLiQ5/CLkWcziiuYMIARHaE8UIIF4i3Y4MThMfxesm8aTrg 2SGmwSpxj8oRg7nZC038gHt6zhT7z951i7eqYxqHINmeKRfp4BwRnBiwQ7ckVusRbGxNviXxF35 4pVFcHLnXX0ZcxyJz+GECDp8dRu0r+HJBOBpeZ/IljJ2NLkUWRUGN2AiSecbtg3/4pjhkpffRxe xpceERUXWeNmSoWw1mzRcbUQpP2nhQ6YPUTkCUYPiAzmja7uYD4jILNM1HYMoU1Qz2GCifH2BgR LBe4Wl3uWyTvIxqyglmWXO+QfsrSKo/SQPLXbAZ5xziK5p6+ix/KSvWZwpUBEn39V1IsT+l7teh ZKuybsfr1Zj0p7FAgaWER1q3g== X-Received: by 2002:a05:600c:4e0d:b0:487:1c2:6a4f with SMTP id 5b1f17b1804b1-487280c2c50mr37164825e9.31.1774612134818; Fri, 27 Mar 2026 04:48:54 -0700 (PDT) Received: from iris-Ian.fritz.box (p200300eb5f28a7005a7787565d4257d0.dip0.t-ipconnect.de. [2003:eb:5f28:a700:5a77:8756:5d42:57d0]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-48722d49c18sm90506115e9.14.2026.03.27.04.48.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 27 Mar 2026 04:48:54 -0700 (PDT) From: iansdannapel@gmail.com To: linux-fpga@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Cc: mdf@kernel.org, yilun.xu@intel.com, trix@redhat.com, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, neil.armstrong@linaro.org, heiko@sntech.de, marex@nabladev.com, prabhakar.mahadev-lad.rj@bp.renesas.com, dev@kael-k.io, Ian Dannapel Subject: [PATCH v6 3/3] fpga-mgr: Add Efinix SPI programming driver Date: Fri, 27 Mar 2026 12:48:41 +0100 Message-ID: <20260327114842.1300284-4-iansdannapel@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260327114842.1300284-1-iansdannapel@gmail.com> References: <20260327114842.1300284-1-iansdannapel@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Ian Dannapel Add a new driver for loading binary firmware to configuration RAM using "SPI passive mode" on Efinix FPGAs. Efinix passive SPI configuration requires chip select to remain asserted from reset until the complete bitstream and trailing idle clocks have been transferred, so the driver keeps CS active with cs_change and locks the SPI bus for the duration of configuration. Signed-off-by: Ian Dannapel --- drivers/fpga/Kconfig | 7 + drivers/fpga/Makefile | 1 + drivers/fpga/efinix-spi.c | 263 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 drivers/fpga/efinix-spi.c diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index 37b35f58f0df..748fc210c135 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -288,6 +288,13 @@ config FPGA_MGR_LATTICE_SYSCONFIG_SPI FPGA manager driver support for Lattice FPGAs programming over slave SPI sysCONFIG interface. =20 +config FPGA_MGR_EFINIX_SPI + tristate "Efinix FPGA configuration over SPI" + depends on SPI + help + FPGA manager driver support for Efinix FPGAs configuration over SPI + (passive mode only). + source "drivers/fpga/tests/Kconfig" =20 endif # FPGA diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index aeb89bb13517..21eb0ef1fc2e 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_FPGA_MGR_VERSAL_FPGA) +=3D versal-fpga.o obj-$(CONFIG_FPGA_MGR_MICROCHIP_SPI) +=3D microchip-spi.o obj-$(CONFIG_FPGA_MGR_LATTICE_SYSCONFIG) +=3D lattice-sysconfig.o obj-$(CONFIG_FPGA_MGR_LATTICE_SYSCONFIG_SPI) +=3D lattice-sysconfig-spi.o +obj-$(CONFIG_FPGA_MGR_EFINIX_SPI) +=3D efinix-spi.o obj-$(CONFIG_ALTERA_PR_IP_CORE) +=3D altera-pr-ip-core.o obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT) +=3D altera-pr-ip-core-plat.o =20 diff --git a/drivers/fpga/efinix-spi.c b/drivers/fpga/efinix-spi.c new file mode 100644 index 000000000000..5cd6de0c5411 --- /dev/null +++ b/drivers/fpga/efinix-spi.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FPGA Manager Driver for Efinix + * + * Copyright (C) 2025 iris-GmbH infrared & intelligent sensors + * + * Ian Dannapel + * + * Load Efinix FPGA firmware over SPI using the serial configuration inter= face. + * + * Note: Only passive mode (host initiates transfer) is currently supporte= d. + */ + +#include +#include +#include +#include +#include +#include + +/* + * 13 dummy bytes generate 104 SPI clock cycles (8 bits each). + * Used to meet the requirement for >100 clock cycles idle sequence. + */ +#define EFINIX_SPI_IDLE_CYCLES_BYTES 13 + +/* + * tDMIN: Minimum time between deassertion of CRESET_N to first + * valid configuration data. (32 =C2=B5s) + */ +#define EFINIX_TDMIN_US_MIN 35 +#define EFINIX_TDMIN_US_MAX 40 + +/* + * tCRESET_N: Minimum CRESET_N low pulse width required to + * trigger re-configuration. (320 ns) + */ +#define EFINIX_TCRESETN_DELAY_MIN_US 1 +#define EFINIX_TCRESETN_DELAY_MAX_US 2 + +/* + * tUSER: Minimum configuration duration after CDONE goes high + * before entering user mode. (25 =C2=B5s) + */ +#define EFINIX_TUSER_US_MIN 30 +#define EFINIX_TUSER_US_MAX 35 + +struct efinix_spi_conf { + struct spi_device *spi; + struct gpio_desc *cdone; + struct gpio_desc *reset; +}; + +static void efinix_spi_reset(struct efinix_spi_conf *conf) +{ + gpiod_set_value(conf->reset, 1); + usleep_range(EFINIX_TCRESETN_DELAY_MIN_US, EFINIX_TCRESETN_DELAY_MAX_US); + gpiod_set_value(conf->reset, 0); + usleep_range(EFINIX_TDMIN_US_MIN, EFINIX_TDMIN_US_MAX); +} + +static enum fpga_mgr_states efinix_spi_state(struct fpga_manager *mgr) +{ + struct efinix_spi_conf *conf =3D mgr->priv; + + if (conf->cdone && gpiod_get_value(conf->cdone) =3D=3D 1) + return FPGA_MGR_STATE_OPERATING; + + return FPGA_MGR_STATE_UNKNOWN; +} + +static int efinix_spi_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct device *dev =3D &mgr->dev; + struct efinix_spi_conf *conf =3D mgr->priv; + struct spi_transfer assert_cs =3D { + .cs_change =3D 1, + }; + struct spi_message message; + int ret; + + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { + dev_err(dev, "Partial reconfiguration not supported\n"); + return -EOPNOTSUPP; + } + + /* + * Efinix passive SPI configuration requires chip select to stay + * asserted from reset until the bitstream is fully clocked in. + * Lock the SPI bus so no other device can toggle CS between the + * reset pulse and the write/complete transfers. + */ + spi_bus_lock(conf->spi->controller); + spi_message_init_with_transfers(&message, &assert_cs, 1); + ret =3D spi_sync_locked(conf->spi, &message); + if (ret) { + spi_bus_unlock(conf->spi->controller); + return ret; + } + + /* Reset with CS asserted */ + efinix_spi_reset(conf); + + return 0; +} + +static int efinix_spi_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct device *dev =3D &mgr->dev; + struct spi_transfer write_xfer =3D { + .tx_buf =3D buf, + .len =3D count, + .cs_change =3D 1, /* Keep CS asserted */ + }; + struct efinix_spi_conf *conf =3D mgr->priv; + struct spi_message message; + int ret; + + spi_message_init_with_transfers(&message, &write_xfer, 1); + ret =3D spi_sync_locked(conf->spi, &message); + if (ret) { + dev_err(dev, "SPI error in firmware write: %d\n", ret); + spi_bus_unlock(conf->spi->controller); + } + + return ret; +} + +static int efinix_spi_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + unsigned long timeout =3D + jiffies + usecs_to_jiffies(info->config_complete_timeout_us); + struct spi_transfer clk_cycles =3D { + .len =3D EFINIX_SPI_IDLE_CYCLES_BYTES, + /* Release CS after the trailing idle clocks are sent. */ + .cs_change =3D 0, + }; + struct efinix_spi_conf *conf =3D mgr->priv; + struct spi_message message; + int done, ret; + bool expired =3D false; + u8 *dummy_buf; + + dummy_buf =3D kzalloc(EFINIX_SPI_IDLE_CYCLES_BYTES, GFP_KERNEL); + if (!dummy_buf) { + ret =3D -ENOMEM; + goto unlock_spi; + } + + /* + * Keep the bus locked while sending the trailing idle clocks, then + * let this final transfer deassert CS to terminate configuration. + */ + clk_cycles.tx_buf =3D dummy_buf; + spi_message_init_with_transfers(&message, &clk_cycles, 1); + ret =3D spi_sync_locked(conf->spi, &message); + if (ret) { + dev_err(&mgr->dev, "SPI error in write complete: %d\n", ret); + goto free_buf; + } + + if (conf->cdone) { + while (!expired) { + done =3D gpiod_get_value(conf->cdone); + if (done < 0) { + ret =3D done; + goto free_buf; + } + if (done) + break; + + usleep_range(10, 20); + expired =3D time_after(jiffies, timeout); + } + + if (expired) { + dev_err(&mgr->dev, "Timeout waiting for CDONE\n"); + ret =3D -ETIMEDOUT; + goto free_buf; + } + } + + usleep_range(EFINIX_TUSER_US_MIN, EFINIX_TUSER_US_MAX); + +free_buf: + kfree(dummy_buf); +unlock_spi: + spi_bus_unlock(conf->spi->controller); + + return ret; +} + +static const struct fpga_manager_ops efinix_spi_ops =3D { + .state =3D efinix_spi_state, + .write_init =3D efinix_spi_write_init, + .write =3D efinix_spi_write, + .write_complete =3D efinix_spi_write_complete, +}; + +static int efinix_spi_probe(struct spi_device *spi) +{ + struct efinix_spi_conf *conf; + struct fpga_manager *mgr; + + if (!(spi->mode & SPI_CPHA) || !(spi->mode & SPI_CPOL)) + return dev_err_probe(&spi->dev, -EINVAL, + "Unsupported SPI mode, set CPHA and CPOL\n"); + + conf =3D devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL); + if (!conf) + return -ENOMEM; + + conf->reset =3D devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(conf->reset)) + return dev_err_probe(&spi->dev, PTR_ERR(conf->reset), + "Failed to get RESET gpio\n"); + + conf->cdone =3D devm_gpiod_get_optional(&spi->dev, "cdone", GPIOD_IN); + if (IS_ERR(conf->cdone)) + return dev_err_probe(&spi->dev, PTR_ERR(conf->cdone), + "Failed to get CDONE gpio\n"); + + conf->spi =3D spi; + + mgr =3D devm_fpga_mgr_register(&spi->dev, + "Efinix FPGA Manager", + &efinix_spi_ops, conf); + + return PTR_ERR_OR_ZERO(mgr); +} + +static const struct of_device_id efinix_spi_of_match[] =3D { + { .compatible =3D "efinix,trion-config", }, + {} +}; +MODULE_DEVICE_TABLE(of, efinix_spi_of_match); + +static const struct spi_device_id efinix_ids[] =3D { + { "trion-config", 0 }, + { "titanium-config", 0 }, + { "topaz-config", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(spi, efinix_ids); + +static struct spi_driver efinix_spi_driver =3D { + .driver =3D { + .name =3D "efinix-spi", + .of_match_table =3D efinix_spi_of_match, + }, + .probe =3D efinix_spi_probe, + .id_table =3D efinix_ids, +}; + +module_spi_driver(efinix_spi_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ian Dannapel "); +MODULE_DESCRIPTION("Efinix FPGA SPI Programming Driver"); --=20 2.43.0