// 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");