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. +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" endif # FPGA diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index aeb89bb13517..6f5798b27e0d 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_FPGA_MGR_LATTICE_SYSCONFIG) += lattice-sysconfig.o obj-$(CONFIG_FPGA_MGR_LATTICE_SYSCONFIG_SPI) += lattice-sysconfig-spi.o obj-$(CONFIG_ALTERA_PR_IP_CORE) += altera-pr-ip-core.o obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT) += altera-pr-ip-core-plat.o +obj-$(CONFIG_FPGA_MGR_EFINIX_SPI) += efinix-spi.o # FPGA Secure Update Drivers obj-$(CONFIG_FPGA_M10_BMC_SEC_UPDATE) += intel-m10-bmc-sec-update.o diff --git a/drivers/fpga/efinix-spi.c b/drivers/fpga/efinix-spi.c new file mode 100644 index 000000000000..ed9a41232a32 --- /dev/null +++ b/drivers/fpga/efinix-spi.c @@ -0,0 +1,260 @@ +// 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 interface. + * + * Note: Only passive mode (host initiates transfer) is currently supported. + */ + +#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 µs) + */ +#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 µs) + */ +#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 = 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) +{ + struct efinix_spi_conf *conf = mgr->priv; + struct spi_transfer assert_cs = { + /* Keep CS asserted across configuration. */ + .cs_change = 1, + }; + struct spi_message message; + int ret; + + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { + dev_err(&mgr->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 = 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 spi_transfer write_xfer = { + .tx_buf = buf, + .len = count, + .cs_change = 1, + }; + struct efinix_spi_conf *conf = mgr->priv; + struct spi_message message; + int ret; + + spi_message_init_with_transfers(&message, &write_xfer, 1); + ret = spi_sync_locked(conf->spi, &message); + if (ret) { + dev_err(&mgr->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 = + jiffies + usecs_to_jiffies(info->config_complete_timeout_us); + struct spi_transfer clk_cycles = { + .len = EFINIX_SPI_IDLE_CYCLES_BYTES, + /* Release CS after the trailing idle clocks are sent. */ + .cs_change = 0, + }; + struct efinix_spi_conf *conf = mgr->priv; + struct spi_message message; + int done, ret; + bool expired = false; + u8 *dummy_buf; + + dummy_buf = kzalloc(EFINIX_SPI_IDLE_CYCLES_BYTES, GFP_KERNEL); + if (!dummy_buf) { + ret = -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 = dummy_buf; + spi_message_init_with_transfers(&message, &clk_cycles, 1); + ret = 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 = gpiod_get_value(conf->cdone); + if (done < 0) { + ret = done; + goto free_buf; + } + if (done) + break; + + usleep_range(10, 20); + expired = time_after(jiffies, timeout); + } + + if (expired) { + dev_err(&mgr->dev, "Timeout waiting for CDONE\n"); + ret = -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 = { + .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; + + 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 = devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL); + if (!conf) + return -ENOMEM; + + 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"); + + 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"); + + conf->spi = spi; + + 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-config", }, + {} +}; +MODULE_DEVICE_TABLE(of, efinix_spi_of_match); + +static const struct spi_device_id efinix_ids[] = { + { "trion-config", 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 "); +MODULE_DESCRIPTION("Efinix FPGA SPI Programming Driver");