Message ID | 20250228094732.54642-4-iansdannapel@gmail.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | Add Efinix FPGA SPI programming support | expand |
[AMD Official Use Only - AMD Internal Distribution Only] > -----Original Message----- > From: iansdannapel@gmail.com <iansdannapel@gmail.com> > Sent: Friday, February 28, 2025 3:18 PM > To: linux-fpga@vger.kernel.org > Cc: Moritz Fischer <mdf@kernel.org>; Wu Hao <hao.wu@intel.com>; Xu Yilun > <yilun.xu@intel.com>; Tom Rix <trix@redhat.com>; Rob Herring > <robh@kernel.org>; Krzysztof Kozlowski <krzk+dt@kernel.org>; Conor Dooley > <conor+dt@kernel.org>; Neil Armstrong <neil.armstrong@linaro.org>; Jonathan > Cameron <Jonathan.Cameron@huawei.com>; Rafał Miłecki <rafal@milecki.pl>; > Aradhya Bhatia <a-bhatia1@ti.com>; Ian Dannapel <iansdannapel@gmail.com>; > open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS > <devicetree@vger.kernel.org>; open list <linux-kernel@vger.kernel.org> > Subject: [v4 3/3] fpga-mgr: Add Efinix SPI programming driver > > From: Ian Dannapel <iansdannapel@gmail.com> > > Add a new driver for loading binary firmware to configuration RAM using "SPI > passive mode" on Efinix FPGAs. > > Signed-off-by: Ian Dannapel <iansdannapel@gmail.com> > --- > drivers/fpga/Kconfig | 7 ++ > drivers/fpga/Makefile | 1 + > drivers/fpga/efinix-spi.c | 212 ++++++++++++++++++++++++++++++++++++++ > 3 files changed, 220 insertions(+) > create mode 100644 drivers/fpga/efinix-spi.c > > diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index > 37b35f58f0df..b5d60ba62900 100644 > --- a/drivers/fpga/Kconfig > +++ b/drivers/fpga/Kconfig > @@ -83,6 +83,13 @@ config FPGA_MGR_XILINX_SPI > FPGA manager driver support for Xilinx FPGA configuration > over slave serial interface. > > +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). > + > config FPGA_MGR_ICE40_SPI > tristate "Lattice iCE40 SPI" > depends on OF && SPI > diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index > aeb89bb13517..adbd51d2cd1e 100644 > --- a/drivers/fpga/Makefile > +++ b/drivers/fpga/Makefile > @@ -18,6 +18,7 @@ obj-$(CONFIG_FPGA_MGR_TS73XX) += > ts73xx-fpga.o > obj-$(CONFIG_FPGA_MGR_XILINX_CORE) += xilinx-core.o > obj-$(CONFIG_FPGA_MGR_XILINX_SELECTMAP) += xilinx-selectmap.o > obj-$(CONFIG_FPGA_MGR_XILINX_SPI) += xilinx-spi.o > +obj-$(CONFIG_FPGA_MGR_EFINIX_SPI) += efinix-spi.o > obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o > obj-$(CONFIG_FPGA_MGR_ZYNQMP_FPGA) += zynqmp-fpga.o > obj-$(CONFIG_FPGA_MGR_VERSAL_FPGA) += versal-fpga.o > diff --git a/drivers/fpga/efinix-spi.c b/drivers/fpga/efinix-spi.c new file mode 100644 > index 000000000000..07885110a8a8 > --- /dev/null > +++ b/drivers/fpga/efinix-spi.c > @@ -0,0 +1,212 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * FPGA Manager Driver for Efinix > + * > + * Copyright (C) 2025 iris-GmbH infrared & intelligent sensors > + * > + * Ian Dannapel <iansdannapel@gmail.com> > + * > + * Load Efinix FPGA firmware over SPI using the serial configuration interface. > + * > + * Note 1: Only passive mode (host initiates transfer) is currently supported. > + * Note 2: Topaz and Titanium support is based on documentation but > +remains > + * untested. > + */ > + > +#include <linux/delay.h> > +#include <linux/fpga/fpga-mgr.h> > +#include <linux/gpio/consumer.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/spi/spi.h> > + > +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); > + /* tCRESET_N > 320 ns */ > + usleep_range(1, 2); > + gpiod_set_value(conf->reset, 0); > + > + /* tDMIN > 32 us */ > + usleep_range(35, 40); Use macros instead of hardcoded values. > +} > + > +static enum fpga_mgr_states efinix_spi_state(struct fpga_manager *mgr) > +{ > + struct efinix_spi_conf *conf = mgr->priv; > + > + if (conf->cdone && gpiod_get_value(conf->cdone) == 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) > +{ > + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { > + dev_err(&mgr->dev, "Partial reconfiguration not supported\n"); > + return -EOPNOTSUPP; > + } > + return 0; > +} > + > +static int efinix_spi_write(struct fpga_manager *mgr, const char *buf, > + size_t count) > +{ > + struct efinix_spi_conf *conf = mgr->priv; > + int ret; > + struct spi_message message; > + struct spi_transfer assert_cs = { > + .cs_change = 1 > + }; > + struct spi_transfer write_xfer = { > + .tx_buf = buf, > + .len = count > + }; > + struct spi_transfer clk_cycles = { > + .len = 13, // > 100 clock cycles The .len = 13 is based on documentation stating? Consider using macro. > + .tx_buf = NULL > + }; > + u8 *dummy_buf; > + > + dummy_buf = kzalloc(13, GFP_KERNEL); Same - use macro > + if (!dummy_buf) { > + ret = -ENOMEM; > + goto fail; > + } > + > + spi_bus_lock(conf->spi->controller); > + spi_message_init(&message); > + spi_message_add_tail(&assert_cs, &message); > + ret = spi_sync_locked(conf->spi, &message); > + if (ret) > + goto fail_unlock; > + > + /* reset with asserted cs */ > + efinix_spi_reset(conf); > + > + spi_message_init(&message); > + spi_message_add_tail(&write_xfer, &message); > + > + clk_cycles.tx_buf = dummy_buf; > + spi_message_add_tail(&clk_cycles, &message); > + > + ret = spi_sync_locked(conf->spi, &message); > + if (ret) > + dev_err(&mgr->dev, "SPI error in firmware write: %d\n", ret); > + > +fail_unlock: > + spi_bus_unlock(conf->spi->controller); > + kfree(dummy_buf); > +fail: > + return ret; > +} > + > +static int efinix_spi_write_complete(struct fpga_manager *mgr, > + struct fpga_image_info *info) > +{ > + struct efinix_spi_conf *conf = mgr->priv; > + unsigned long timeout = > + jiffies + usecs_to_jiffies(info->config_complete_timeout_us); > + bool expired = false; > + int done; > + > + if (conf->cdone) { > + while (!expired) { > + expired = time_after(jiffies, timeout); > + > + done = gpiod_get_value(conf->cdone); > + if (done < 0) > + return done; > + > + if (done) > + break; > + } > + } > + > + if (expired) > + return -ETIMEDOUT; > + > + /* tUSER > 25 us */ > + usleep_range(30, 35); Same - use macros. > + return 0; > +} > + > +static const struct fpga_manager_ops efinix_spi_ops = { > + .state = efinix_spi_state, > + .write_init = efinix_spi_write_init, > + .write = efinix_spi_write, > + .write_complete = efinix_spi_write_complete, }; > + > +static int efinix_spi_probe(struct spi_device *spi) { > + struct efinix_spi_conf *conf; > + struct fpga_manager *mgr; > + > + conf = devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL); > + if (!conf) > + return -ENOMEM; > + > + conf->spi = spi; > + > + conf->reset = 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"); > + > + 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->cdone = 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"); > + > + mgr = 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[] = { > + { .compatible = "efinix,trion-spi", }, > + { .compatible = "efinix,titanium-spi", }, > + { .compatible = "efinix,topaz-spi", }, > + { .compatible = "efinix,fpga-spi", }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, efinix_spi_of_match); > + > +static const struct spi_device_id efinix_ids[] = { > + { "trion-spi", 0 }, > + { "titanium-spi", 0 }, > + { "topaz-spi", 0 }, > + {}, > +}; > +MODULE_DEVICE_TABLE(spi, efinix_ids); > + > +static struct spi_driver efinix_spi_driver = { > + .driver = { > + .name = "efinix-spi", > + .of_match_table = efinix_spi_of_match, > + }, > + .probe = efinix_spi_probe, > + .id_table = efinix_ids, > +}; > + > +module_spi_driver(efinix_spi_driver); > + > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("Ian Dannapel <iansdannapel@gmail.com>"); > +MODULE_DESCRIPTION("Efinix FPGA SPI Programming Driver (Topaz/Titanium > +untested)"); If untested, it might be useful to mark them as experimental in Kconfig. Regards, Navakishore.
Hi Navakishore, thanks for the review. On Mon, Mar 3, 2025 at 12:57 PM Manne, Nava kishore <nava.kishore.manne@amd.com> wrote: > > +MODULE_DESCRIPTION("Efinix FPGA SPI Programming Driver (Topaz/Titanium > > +untested)"); > > If untested, it might be useful to mark them as experimental in Kconfig. Is a separate Kconfig for that preferred or maybe just a note in the help description? Regards, Ian
diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index 37b35f58f0df..b5d60ba62900 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -83,6 +83,13 @@ config FPGA_MGR_XILINX_SPI FPGA manager driver support for Xilinx FPGA configuration over slave serial interface. +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). + config FPGA_MGR_ICE40_SPI tristate "Lattice iCE40 SPI" depends on OF && SPI diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index aeb89bb13517..adbd51d2cd1e 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o obj-$(CONFIG_FPGA_MGR_XILINX_CORE) += xilinx-core.o obj-$(CONFIG_FPGA_MGR_XILINX_SELECTMAP) += xilinx-selectmap.o obj-$(CONFIG_FPGA_MGR_XILINX_SPI) += xilinx-spi.o +obj-$(CONFIG_FPGA_MGR_EFINIX_SPI) += efinix-spi.o obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o obj-$(CONFIG_FPGA_MGR_ZYNQMP_FPGA) += zynqmp-fpga.o obj-$(CONFIG_FPGA_MGR_VERSAL_FPGA) += versal-fpga.o diff --git a/drivers/fpga/efinix-spi.c b/drivers/fpga/efinix-spi.c new file mode 100644 index 000000000000..07885110a8a8 --- /dev/null +++ b/drivers/fpga/efinix-spi.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FPGA Manager Driver for Efinix + * + * Copyright (C) 2025 iris-GmbH infrared & intelligent sensors + * + * Ian Dannapel <iansdannapel@gmail.com> + * + * Load Efinix FPGA firmware over SPI using the serial configuration interface. + * + * Note 1: Only passive mode (host initiates transfer) is currently supported. + * Note 2: Topaz and Titanium support is based on documentation but remains + * untested. + */ + +#include <linux/delay.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/spi/spi.h> + +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); + /* tCRESET_N > 320 ns */ + usleep_range(1, 2); + gpiod_set_value(conf->reset, 0); + + /* tDMIN > 32 us */ + usleep_range(35, 40); +} + +static enum fpga_mgr_states efinix_spi_state(struct fpga_manager *mgr) +{ + struct efinix_spi_conf *conf = mgr->priv; + + if (conf->cdone && gpiod_get_value(conf->cdone) == 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) +{ + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { + dev_err(&mgr->dev, "Partial reconfiguration not supported\n"); + return -EOPNOTSUPP; + } + return 0; +} + +static int efinix_spi_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct efinix_spi_conf *conf = mgr->priv; + int ret; + struct spi_message message; + struct spi_transfer assert_cs = { + .cs_change = 1 + }; + struct spi_transfer write_xfer = { + .tx_buf = buf, + .len = count + }; + struct spi_transfer clk_cycles = { + .len = 13, // > 100 clock cycles + .tx_buf = NULL + }; + u8 *dummy_buf; + + dummy_buf = kzalloc(13, GFP_KERNEL); + if (!dummy_buf) { + ret = -ENOMEM; + goto fail; + } + + spi_bus_lock(conf->spi->controller); + spi_message_init(&message); + spi_message_add_tail(&assert_cs, &message); + ret = spi_sync_locked(conf->spi, &message); + if (ret) + goto fail_unlock; + + /* reset with asserted cs */ + efinix_spi_reset(conf); + + spi_message_init(&message); + spi_message_add_tail(&write_xfer, &message); + + clk_cycles.tx_buf = dummy_buf; + spi_message_add_tail(&clk_cycles, &message); + + ret = spi_sync_locked(conf->spi, &message); + if (ret) + dev_err(&mgr->dev, "SPI error in firmware write: %d\n", ret); + +fail_unlock: + spi_bus_unlock(conf->spi->controller); + kfree(dummy_buf); +fail: + return ret; +} + +static int efinix_spi_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct efinix_spi_conf *conf = mgr->priv; + unsigned long timeout = + jiffies + usecs_to_jiffies(info->config_complete_timeout_us); + bool expired = false; + int done; + + if (conf->cdone) { + while (!expired) { + expired = time_after(jiffies, timeout); + + done = gpiod_get_value(conf->cdone); + if (done < 0) + return done; + + if (done) + break; + } + } + + if (expired) + return -ETIMEDOUT; + + /* tUSER > 25 us */ + usleep_range(30, 35); + return 0; +} + +static const struct fpga_manager_ops efinix_spi_ops = { + .state = efinix_spi_state, + .write_init = efinix_spi_write_init, + .write = efinix_spi_write, + .write_complete = efinix_spi_write_complete, +}; + +static int efinix_spi_probe(struct spi_device *spi) +{ + struct efinix_spi_conf *conf; + struct fpga_manager *mgr; + + conf = devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL); + if (!conf) + return -ENOMEM; + + conf->spi = spi; + + conf->reset = 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"); + + 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->cdone = 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"); + + mgr = 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[] = { + { .compatible = "efinix,trion-spi", }, + { .compatible = "efinix,titanium-spi", }, + { .compatible = "efinix,topaz-spi", }, + { .compatible = "efinix,fpga-spi", }, + {} +}; +MODULE_DEVICE_TABLE(of, efinix_spi_of_match); + +static const struct spi_device_id efinix_ids[] = { + { "trion-spi", 0 }, + { "titanium-spi", 0 }, + { "topaz-spi", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(spi, efinix_ids); + +static struct spi_driver efinix_spi_driver = { + .driver = { + .name = "efinix-spi", + .of_match_table = efinix_spi_of_match, + }, + .probe = efinix_spi_probe, + .id_table = efinix_ids, +}; + +module_spi_driver(efinix_spi_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ian Dannapel <iansdannapel@gmail.com>"); +MODULE_DESCRIPTION("Efinix FPGA SPI Programming Driver (Topaz/Titanium untested)");