drivers: fpga: separate drivers of iCE40 for SPI and GPIO bitbang

Separate the current driver for the FPGA iCE40 into two different ones.
One implements only the SPI load mode, the other one only the GPIO
bitbang mode.

Signed-off-by: Benedikt Schmidt <benedikt.schmidt@embedded-solutions.at>
This commit is contained in:
Benedikt Schmidt 2024-11-26 08:29:36 +01:00 committed by Fabio Baltieri
commit 760210f39d
12 changed files with 810 additions and 720 deletions

View file

@ -130,6 +130,12 @@ Drivers and Sensors
* Flash
* FPGA
* Extracted from :dtcompatible:`lattice,ice40-fpga` the compatible and driver for
:dtcompatible:`lattice,ice40-fpga-bitbang`. This replaces the original ``load_mode`` property from
the binding, which selected either the SPI or GPIO bitbang load mode.
* GNSS
* GPIO

View file

@ -6,7 +6,9 @@ zephyr_library_sources_ifdef(CONFIG_FPGA_SHELL fpga_shell.c)
zephyr_library_sources_ifdef(CONFIG_ALTERA_AGILEX_BRIDGE_FPGA fpga_altera_agilex_bridge.c)
zephyr_library_sources_ifdef(CONFIG_EOS_S3_FPGA fpga_eos_s3.c)
zephyr_library_sources_ifdef(CONFIG_ICE40_FPGA fpga_ice40.c)
zephyr_library_sources_ifdef(CONFIG_ICE40_FPGA fpga_ice40_common.c)
zephyr_library_sources_ifdef(CONFIG_ICE40_FPGA_SPI fpga_ice40_spi.c)
zephyr_library_sources_ifdef(CONFIG_ICE40_FPGA_BITBANG fpga_ice40_bitbang.c)
zephyr_library_sources_ifdef(CONFIG_MPFS_FPGA fpga_mpfs.c)
zephyr_library_sources_ifdef(CONFIG_ZYNQMP_FPGA fpga_zynqmp.c)
zephyr_library_sources_ifdef(CONFIG_SLG471X5_FPGA fpga_slg471x5.c)

View file

@ -1,10 +1,31 @@
# Copyright (c) 2022 Meta
# SPDX-License-Identifier: Apache-2.0
config ICE40_FPGA
bool "Lattice iCE40 fpga driver [EXPERIMENTAL]"
select EXPERIMENTAL
menuconfig ICE40_FPGA
bool "Lattice iCE40 fpga driver"
default y
depends on (DT_HAS_LATTICE_ICE40_FPGA_ENABLED || \
DT_HAS_LATTICE_ICE40_FPGA_BITBANG_ENABLED)
imply CRC
depends on SPI
help
Enable support for the Lattice iCE40 fpga driver.
if ICE40_FPGA
config ICE40_FPGA_SPI
bool "Lattice iCE40 fpga SPI driver"
default y
depends on DT_HAS_LATTICE_ICE40_FPGA_ENABLED
help
Enable support for the Lattice iCE40 fpga SPI driver.
config ICE40_FPGA_BITBANG
bool "Lattice iCE40 fpga driver GPIO bitbang"
default y
select EXPERIMENTAL
depends on DT_HAS_LATTICE_ICE40_FPGA_BITBANG_ENABLED
help
Enable support for the Lattice iCE40 fpga GPIO bitbang driver.
endif # ICE40_FPGA

View file

@ -1,626 +0,0 @@
/*
* Copyright (c) 2022 Meta
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT lattice_ice40_fpga
#include <stdbool.h>
#include <stdio.h>
#include <zephyr/device.h>
#include <zephyr/drivers/fpga.h>
#include <zephyr/drivers/gpio.h>
#ifdef CONFIG_PINCTRL
#include <zephyr/drivers/pinctrl.h>
#endif
#include <zephyr/drivers/spi.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/crc.h>
#include <zephyr/sys/util.h>
/*
* Note: When loading a bitstream, the iCE40 has a 'quirk' in that the CS
* polarity must be inverted during the 'leading clocks' phase and
* 'trailing clocks' phase. While the bitstream is being transmitted, the
* CS polarity is normal (active low). Zephyr's SPI driver model currently
* does not handle these types of quirks (in contrast to e.g. Linux).
*
* The logical alternative would be to put the CS into GPIO mode, perform 3
* separate SPI transfers (inverting CS polarity as necessary) and then
* restore the default pinctrl settings. On some higher-end microcontrollers
* and microprocessors, it's possible to do that without breaking the iCE40
* timing requirements.
*
* However, on lower-end microcontrollers, the amount of time that elapses
* between SPI transfers does break the iCE40 timing requirements. That
* leaves us with the bitbanging option. Of course, on lower-end
* microcontrollers, the amount of time required to execute something
* like gpio_pin_configure_dt() dwarfs the 2*500 nanoseconds needed to
* achieve the minimum 1 MHz clock rate for loading the iCE40 bistream. So
* in order to bitbang on lower-end microcontrollers, we actually require
* direct register access to the set and clear registers.
*/
/*
* Values in Hz, intentionally to be comparable with the spi-max-frequency
* property from DT bindings in spi-device.yaml.
*/
#define FPGA_ICE40_SPI_HZ_MIN 1000000
#define FPGA_ICE40_SPI_HZ_MAX 25000000
#define FPGA_ICE40_CRESET_DELAY_US_MIN 1 /* 200ns absolute minimum */
#define FPGA_ICE40_CONFIG_DELAY_US_MIN 1200
#define FPGA_ICE40_LEADING_CLOCKS_MIN 8
#define FPGA_ICE40_TRAILING_CLOCKS_MIN 49
LOG_MODULE_REGISTER(fpga_ice40, CONFIG_FPGA_LOG_LEVEL);
struct fpga_ice40_data {
uint32_t crc;
/* simply use crc32 as info */
char info[2 * sizeof(uint32_t) + 1];
bool on;
bool loaded;
struct k_spinlock lock;
};
struct fpga_ice40_config {
struct spi_dt_spec bus;
struct gpio_dt_spec cdone;
struct gpio_dt_spec creset;
struct gpio_dt_spec clk;
struct gpio_dt_spec pico;
volatile gpio_port_pins_t *set;
volatile gpio_port_pins_t *clear;
uint16_t mhz_delay_count;
uint16_t creset_delay_us;
uint16_t config_delay_us;
uint8_t leading_clocks;
uint8_t trailing_clocks;
fpga_api_load load;
#ifdef CONFIG_PINCTRL
const struct pinctrl_dev_config *pincfg;
#endif
};
static void fpga_ice40_crc_to_str(uint32_t crc, char *s)
{
char ch;
uint8_t i;
uint8_t nibble;
const char *table = "0123456789abcdef";
for (i = 0; i < sizeof(crc) * NIBBLES_PER_BYTE; ++i, crc >>= BITS_PER_NIBBLE) {
nibble = crc & GENMASK(BITS_PER_NIBBLE, 0);
ch = table[nibble];
s[sizeof(crc) * NIBBLES_PER_BYTE - i - 1] = ch;
}
s[sizeof(crc) * NIBBLES_PER_BYTE] = '\0';
}
/*
* This is a calibrated delay loop used to achieve a 1 MHz SPI_CLK frequency
* with the bitbang mode. It is used both in fpga_ice40_send_clocks()
* and fpga_ice40_spi_send_data().
*
* Calibration is achieved via the mhz_delay_count device tree parameter. See
* lattice,ice40-fpga.yaml for details.
*/
static inline void fpga_ice40_delay(size_t n)
{
for (; n > 0; --n) {
__asm__ __volatile__("");
}
}
static void fpga_ice40_send_clocks(size_t delay, volatile gpio_port_pins_t *set,
volatile gpio_port_pins_t *clear, gpio_port_pins_t clk, size_t n)
{
for (; n > 0; --n) {
*clear |= clk;
fpga_ice40_delay(delay);
*set |= clk;
fpga_ice40_delay(delay);
}
}
static void fpga_ice40_spi_send_data(size_t delay, volatile gpio_port_pins_t *set,
volatile gpio_port_pins_t *clear, gpio_port_pins_t cs,
gpio_port_pins_t clk, gpio_port_pins_t pico, uint8_t *z,
size_t n)
{
bool hi;
/* assert chip-select (active low) */
*clear |= cs;
for (; n > 0; --n, ++z) {
/* msb down to lsb */
for (int b = 7; b >= 0; --b) {
/* Data is shifted out on the falling edge (CPOL=0) */
*clear |= clk;
fpga_ice40_delay(delay);
hi = !!(BIT(b) & *z);
if (hi) {
*set |= pico;
} else {
*clear |= pico;
}
/* Data is sampled on the rising edge (CPHA=0) */
*set |= clk;
fpga_ice40_delay(delay);
}
}
/* de-assert chip-select (active low) */
*set |= cs;
}
static enum FPGA_status fpga_ice40_get_status(const struct device *dev)
{
enum FPGA_status st;
k_spinlock_key_t key;
struct fpga_ice40_data *data = dev->data;
key = k_spin_lock(&data->lock);
if (data->loaded && data->on) {
st = FPGA_STATUS_ACTIVE;
} else {
st = FPGA_STATUS_INACTIVE;
}
k_spin_unlock(&data->lock, key);
return st;
}
/*
* See iCE40 Family Handbook, Appendix A. SPI Slave Configuration Procedure,
* pp 15-21.
*
* https://www.latticesemi.com/~/media/LatticeSemi/Documents/Handbooks/iCE40FamilyHandbook.pdf
*/
static int fpga_ice40_load_gpio(const struct device *dev, uint32_t *image_ptr, uint32_t img_size)
{
int ret;
uint32_t crc;
gpio_port_pins_t cs;
gpio_port_pins_t clk;
k_spinlock_key_t key;
gpio_port_pins_t pico;
gpio_port_pins_t creset;
struct fpga_ice40_data *data = dev->data;
const struct fpga_ice40_config *config = dev->config;
if (!device_is_ready(config->clk.port)) {
LOG_ERR("%s: GPIO for clk is not ready", dev->name);
return -ENODEV;
}
if (!device_is_ready(config->pico.port)) {
LOG_ERR("%s: GPIO for pico is not ready", dev->name);
return -ENODEV;
}
/* prepare masks */
cs = BIT(config->bus.config.cs.gpio.pin);
clk = BIT(config->clk.pin);
pico = BIT(config->pico.pin);
creset = BIT(config->creset.pin);
/* crc check */
crc = crc32_ieee((uint8_t *)image_ptr, img_size);
if (data->loaded && crc == data->crc) {
LOG_WRN("already loaded with image CRC32c: 0x%08x", data->crc);
}
key = k_spin_lock(&data->lock);
/* clear crc */
data->crc = 0;
data->loaded = false;
fpga_ice40_crc_to_str(0, data->info);
LOG_DBG("Initializing GPIO");
ret = gpio_pin_configure_dt(&config->cdone, GPIO_INPUT) ||
gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_HIGH) ||
gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_HIGH) ||
gpio_pin_configure_dt(&config->clk, GPIO_OUTPUT_HIGH) ||
gpio_pin_configure_dt(&config->pico, GPIO_OUTPUT_HIGH);
__ASSERT(ret == 0, "Failed to initialize GPIO: %d", ret);
LOG_DBG("Set CRESET low");
LOG_DBG("Set SPI_CS low");
*config->clear |= (creset | cs);
/* Wait a minimum of 200ns */
LOG_DBG("Delay %u us", config->creset_delay_us);
fpga_ice40_delay(2 * config->mhz_delay_count * config->creset_delay_us);
if (gpio_pin_get_dt(&config->cdone) != 0) {
LOG_ERR("CDONE should be low after the reset");
ret = -EIO;
goto unlock;
}
LOG_DBG("Set CRESET high");
*config->set |= creset;
LOG_DBG("Delay %u us", config->config_delay_us);
k_busy_wait(config->config_delay_us);
LOG_DBG("Set SPI_CS high");
*config->set |= cs;
LOG_DBG("Send %u clocks", config->leading_clocks);
fpga_ice40_send_clocks(config->mhz_delay_count, config->set, config->clear, clk,
config->leading_clocks);
LOG_DBG("Set SPI_CS low");
LOG_DBG("Send bin file");
LOG_DBG("Set SPI_CS high");
fpga_ice40_spi_send_data(config->mhz_delay_count, config->set, config->clear, cs, clk, pico,
(uint8_t *)image_ptr, img_size);
LOG_DBG("Send %u clocks", config->trailing_clocks);
fpga_ice40_send_clocks(config->mhz_delay_count, config->set, config->clear, clk,
config->trailing_clocks);
LOG_DBG("checking CDONE");
ret = gpio_pin_get_dt(&config->cdone);
if (ret < 0) {
LOG_ERR("failed to read CDONE: %d", ret);
goto unlock;
} else if (ret != 1) {
ret = -EIO;
LOG_ERR("CDONE did not go high");
goto unlock;
}
ret = 0;
data->loaded = true;
fpga_ice40_crc_to_str(crc, data->info);
LOG_INF("Loaded image with CRC32 0x%08x", crc);
unlock:
(void)gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_HIGH);
(void)gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_HIGH);
(void)gpio_pin_configure_dt(&config->clk, GPIO_DISCONNECTED);
(void)gpio_pin_configure_dt(&config->pico, GPIO_DISCONNECTED);
#ifdef CONFIG_PINCTRL
(void)pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
#endif
k_spin_unlock(&data->lock, key);
return ret;
}
static int fpga_ice40_load_spi(const struct device *dev, uint32_t *image_ptr, uint32_t img_size)
{
int ret;
uint32_t crc;
k_spinlock_key_t key;
struct spi_buf tx_buf;
const struct spi_buf_set tx_bufs = {
.buffers = &tx_buf,
.count = 1,
};
struct fpga_ice40_data *data = dev->data;
uint8_t clock_buf[(UINT8_MAX + 1) / BITS_PER_BYTE];
const struct fpga_ice40_config *config = dev->config;
struct spi_dt_spec bus;
memcpy(&bus, &config->bus, sizeof(bus));
/*
* Disable the automatism for chip select within the SPI driver,
* as the configuration sequence requires this signal to be inactive
* during the leading and trailing clock phase.
*/
bus.config.cs.gpio.port = NULL;
/* crc check */
crc = crc32_ieee((uint8_t *)image_ptr, img_size);
if (data->loaded && crc == data->crc) {
LOG_WRN("already loaded with image CRC32c: 0x%08x", data->crc);
}
key = k_spin_lock(&data->lock);
/* clear crc */
data->crc = 0;
data->loaded = false;
fpga_ice40_crc_to_str(0, data->info);
LOG_DBG("Initializing GPIO");
ret = gpio_pin_configure_dt(&config->cdone, GPIO_INPUT) ||
gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_HIGH) ||
gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_HIGH);
__ASSERT(ret == 0, "Failed to initialize GPIO: %d", ret);
LOG_DBG("Set CRESET low");
ret = gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_LOW);
if (ret < 0) {
LOG_ERR("failed to set CRESET low: %d", ret);
goto unlock;
}
LOG_DBG("Set SPI_CS low");
ret = gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_LOW);
if (ret < 0) {
LOG_ERR("failed to set SPI_CS low: %d", ret);
goto unlock;
}
/* Wait a minimum of 200ns */
LOG_DBG("Delay %u us", config->creset_delay_us);
k_usleep(config->creset_delay_us);
if (gpio_pin_get_dt(&config->cdone) != 0) {
LOG_ERR("CDONE should be low after the reset");
ret = -EIO;
goto unlock;
}
LOG_DBG("Set CRESET high");
ret = gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_HIGH);
if (ret < 0) {
LOG_ERR("failed to set CRESET high: %d", ret);
goto unlock;
}
LOG_DBG("Delay %u us", config->config_delay_us);
k_busy_wait(config->config_delay_us);
LOG_DBG("Set SPI_CS high");
ret = gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_HIGH);
if (ret < 0) {
LOG_ERR("failed to set SPI_CS high: %d", ret);
goto unlock;
}
LOG_DBG("Send %u clocks", config->leading_clocks);
tx_buf.buf = clock_buf;
tx_buf.len = DIV_ROUND_UP(config->leading_clocks, BITS_PER_BYTE);
ret = spi_write_dt(&bus, &tx_bufs);
if (ret < 0) {
LOG_ERR("Failed to send leading %u clocks: %d", config->leading_clocks, ret);
goto unlock;
}
LOG_DBG("Set SPI_CS low");
ret = gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_LOW);
if (ret < 0) {
LOG_ERR("failed to set SPI_CS low: %d", ret);
goto unlock;
}
LOG_DBG("Send bin file");
tx_buf.buf = image_ptr;
tx_buf.len = img_size;
ret = spi_write_dt(&bus, &tx_bufs);
if (ret < 0) {
LOG_ERR("Failed to send bin file: %d", ret);
goto unlock;
}
LOG_DBG("Set SPI_CS high");
ret = gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_HIGH);
if (ret < 0) {
LOG_ERR("failed to set SPI_CS high: %d", ret);
goto unlock;
}
LOG_DBG("Send %u clocks", config->trailing_clocks);
tx_buf.buf = clock_buf;
tx_buf.len = DIV_ROUND_UP(config->trailing_clocks, BITS_PER_BYTE);
ret = spi_write_dt(&bus, &tx_bufs);
if (ret < 0) {
LOG_ERR("Failed to send trailing %u clocks: %d", config->trailing_clocks, ret);
goto unlock;
}
LOG_DBG("checking CDONE");
ret = gpio_pin_get_dt(&config->cdone);
if (ret < 0) {
LOG_ERR("failed to read CDONE: %d", ret);
goto unlock;
} else if (ret != 1) {
ret = -EIO;
LOG_ERR("CDONE did not go high");
goto unlock;
}
ret = 0;
data->loaded = true;
fpga_ice40_crc_to_str(crc, data->info);
LOG_INF("Loaded image with CRC32 0x%08x", crc);
unlock:
(void)gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_HIGH);
(void)gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_HIGH);
#ifdef CONFIG_PINCTRL
(void)pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
#endif
k_spin_unlock(&data->lock, key);
return ret;
}
static int fpga_ice40_load(const struct device *dev, uint32_t *image_ptr, uint32_t img_size)
{
const struct fpga_ice40_config *config = dev->config;
return config->load(dev, image_ptr, img_size);
}
static int fpga_ice40_on_off(const struct device *dev, bool on)
{
int ret;
k_spinlock_key_t key;
struct fpga_ice40_data *data = dev->data;
const struct fpga_ice40_config *config = dev->config;
key = k_spin_lock(&data->lock);
ret = gpio_pin_configure_dt(&config->creset, on ? GPIO_OUTPUT_HIGH : GPIO_OUTPUT_LOW);
if (ret < 0) {
goto unlock;
}
data->on = on;
ret = 0;
unlock:
k_spin_unlock(&data->lock, key);
return ret;
}
static int fpga_ice40_on(const struct device *dev)
{
return fpga_ice40_on_off(dev, true);
}
static int fpga_ice40_off(const struct device *dev)
{
return fpga_ice40_on_off(dev, false);
}
static int fpga_ice40_reset(const struct device *dev)
{
return fpga_ice40_off(dev) || fpga_ice40_on(dev);
}
static const char *fpga_ice40_get_info(const struct device *dev)
{
struct fpga_ice40_data *data = dev->data;
return data->info;
}
static const struct fpga_driver_api fpga_ice40_api = {
.get_status = fpga_ice40_get_status,
.reset = fpga_ice40_reset,
.load = fpga_ice40_load,
.on = fpga_ice40_on,
.off = fpga_ice40_off,
.get_info = fpga_ice40_get_info,
};
static int fpga_ice40_init(const struct device *dev)
{
int ret;
const struct fpga_ice40_config *config = dev->config;
if (!device_is_ready(config->creset.port)) {
LOG_ERR("%s: GPIO for creset is not ready", dev->name);
return -ENODEV;
}
if (!device_is_ready(config->cdone.port)) {
LOG_ERR("%s: GPIO for cdone is not ready", dev->name);
return -ENODEV;
}
ret = gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_HIGH);
if (ret < 0) {
LOG_ERR("failed to configure CRESET: %d", ret);
return ret;
}
ret = gpio_pin_configure_dt(&config->cdone, GPIO_INPUT);
if (ret < 0) {
LOG_ERR("Failed to initialize CDONE: %d", ret);
return ret;
}
return 0;
}
#define FPGA_ICE40_BUS_FREQ(inst) DT_INST_PROP(inst, spi_max_frequency)
#define FPGA_ICE40_CONFIG_DELAY_US(inst) DT_INST_PROP(inst, config_delay_us)
#define FPGA_ICE40_CRESET_DELAY_US(inst) DT_INST_PROP(inst, creset_delay_us)
#define FPGA_ICE40_LEADING_CLOCKS(inst) DT_INST_PROP(inst, leading_clocks)
#define FPGA_ICE40_TRAILING_CLOCKS(inst) DT_INST_PROP(inst, trailing_clocks)
#define FPGA_ICE40_MHZ_DELAY_COUNT(inst) DT_INST_PROP_OR(inst, mhz_delay_count, 0)
#define FPGA_ICE40_GPIO_PINS(inst, name) (volatile gpio_port_pins_t *)DT_INST_PROP_OR(inst, name, 0)
#define FPGA_ICE40_LOAD_FUNC(inst) \
(DT_INST_PROP(inst, load_mode_bitbang) ? fpga_ice40_load_gpio : fpga_ice40_load_spi)
#ifdef CONFIG_PINCTRL
#define FPGA_ICE40_PINCTRL_CONFIG(inst) .pincfg = PINCTRL_DT_DEV_CONFIG_GET(DT_INST_PARENT(inst)),
#define FPGA_ICE40_PINCTRL_DEFINE(inst) PINCTRL_DT_DEFINE(DT_INST_PARENT(inst))
#else
#define FPGA_ICE40_PINCTRL_CONFIG(inst)
#define FPGA_ICE40_PINCTRL_DEFINE(inst)
#endif
#define FPGA_ICE40_DEFINE(inst) \
BUILD_ASSERT(FPGA_ICE40_BUS_FREQ(inst) >= FPGA_ICE40_SPI_HZ_MIN); \
BUILD_ASSERT(FPGA_ICE40_BUS_FREQ(inst) <= FPGA_ICE40_SPI_HZ_MAX); \
BUILD_ASSERT(FPGA_ICE40_CONFIG_DELAY_US(inst) >= FPGA_ICE40_CONFIG_DELAY_US_MIN); \
BUILD_ASSERT(FPGA_ICE40_CONFIG_DELAY_US(inst) <= UINT16_MAX); \
BUILD_ASSERT(FPGA_ICE40_CRESET_DELAY_US(inst) >= FPGA_ICE40_CRESET_DELAY_US_MIN); \
BUILD_ASSERT(FPGA_ICE40_CRESET_DELAY_US(inst) <= UINT16_MAX); \
BUILD_ASSERT(FPGA_ICE40_LEADING_CLOCKS(inst) >= FPGA_ICE40_LEADING_CLOCKS_MIN); \
BUILD_ASSERT(FPGA_ICE40_LEADING_CLOCKS(inst) <= UINT8_MAX); \
BUILD_ASSERT(FPGA_ICE40_TRAILING_CLOCKS(inst) >= FPGA_ICE40_TRAILING_CLOCKS_MIN); \
BUILD_ASSERT(FPGA_ICE40_TRAILING_CLOCKS(inst) <= UINT8_MAX); \
BUILD_ASSERT(FPGA_ICE40_MHZ_DELAY_COUNT(inst) >= 0); \
BUILD_ASSERT(!DT_INST_PROP(inst, load_mode_bitbang) || \
DT_INST_NODE_HAS_PROP(inst, creset_gpios)); \
BUILD_ASSERT(!DT_INST_PROP(inst, load_mode_bitbang) || \
DT_INST_NODE_HAS_PROP(inst, cdone_gpios)); \
BUILD_ASSERT(!DT_INST_PROP(inst, load_mode_bitbang) || \
DT_INST_NODE_HAS_PROP(inst, clk_gpios)); \
BUILD_ASSERT(!DT_INST_PROP(inst, load_mode_bitbang) || \
DT_INST_NODE_HAS_PROP(inst, pico_gpios)); \
BUILD_ASSERT(!DT_INST_PROP(inst, load_mode_bitbang) || \
DT_INST_NODE_HAS_PROP(inst, gpios_set_reg)); \
BUILD_ASSERT(!DT_INST_PROP(inst, load_mode_bitbang) || \
DT_INST_NODE_HAS_PROP(inst, gpios_clear_reg)); \
\
FPGA_ICE40_PINCTRL_DEFINE(inst); \
static struct fpga_ice40_data fpga_ice40_data_##inst; \
\
static const struct fpga_ice40_config fpga_ice40_config_##inst = { \
.bus = SPI_DT_SPEC_INST_GET(inst, \
SPI_OP_MODE_MASTER | SPI_MODE_CPOL | SPI_MODE_CPHA | \
SPI_WORD_SET(8) | SPI_TRANSFER_MSB, \
0), \
.creset = GPIO_DT_SPEC_INST_GET(inst, creset_gpios), \
.cdone = GPIO_DT_SPEC_INST_GET(inst, cdone_gpios), \
.clk = GPIO_DT_SPEC_INST_GET_OR(inst, clk_gpios, {0}), \
.pico = GPIO_DT_SPEC_INST_GET_OR(inst, pico_gpios, {0}), \
.set = FPGA_ICE40_GPIO_PINS(inst, gpios_set_reg), \
.clear = FPGA_ICE40_GPIO_PINS(inst, gpios_clear_reg), \
.mhz_delay_count = FPGA_ICE40_MHZ_DELAY_COUNT(inst), \
.config_delay_us = FPGA_ICE40_CONFIG_DELAY_US(inst), \
.creset_delay_us = FPGA_ICE40_CRESET_DELAY_US(inst), \
.leading_clocks = FPGA_ICE40_LEADING_CLOCKS(inst), \
.trailing_clocks = FPGA_ICE40_TRAILING_CLOCKS(inst), \
.load = FPGA_ICE40_LOAD_FUNC(inst), \
FPGA_ICE40_PINCTRL_CONFIG(inst)}; \
\
DEVICE_DT_INST_DEFINE(inst, fpga_ice40_init, NULL, &fpga_ice40_data_##inst, \
&fpga_ice40_config_##inst, POST_KERNEL, CONFIG_FPGA_INIT_PRIORITY, \
&fpga_ice40_api);
DT_INST_FOREACH_STATUS_OKAY(FPGA_ICE40_DEFINE)

View file

@ -0,0 +1,278 @@
/*
* Copyright (c) 2022 Meta
* Copyright (c) 2024 SILA Embedded Solutions GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/device.h>
#include <zephyr/drivers/fpga.h>
#include <zephyr/drivers/gpio.h>
#ifdef CONFIG_PINCTRL
#include <zephyr/drivers/pinctrl.h>
#endif /* CONFIG_PINCTRL */
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/crc.h>
#include <zephyr/sys/util.h>
#include "fpga_ice40_common.h"
/*
* Note: When loading a bitstream, the iCE40 has a 'quirk' in that the CS
* polarity must be inverted during the 'leading clocks' phase and
* 'trailing clocks' phase. While the bitstream is being transmitted, the
* CS polarity is normal (active low). Zephyr's SPI driver model currently
* does not handle these types of quirks (in contrast to e.g. Linux).
*
* The logical alternative would be to put the CS into GPIO mode, perform 3
* separate SPI transfers (inverting CS polarity as necessary) and then
* restore the default pinctrl settings. On some higher-end microcontrollers
* and microprocessors, it's possible to do that without breaking the iCE40
* timing requirements.
*
* However, on lower-end microcontrollers, the amount of time that elapses
* between SPI transfers does break the iCE40 timing requirements. That
* leaves us with the bitbanging option. Of course, on lower-end
* microcontrollers, the amount of time required to execute something
* like gpio_pin_configure_dt() dwarfs the 2*500 nanoseconds needed to
* achieve the minimum 1 MHz clock rate for loading the iCE40 bistream. So
* in order to bitbang on lower-end microcontrollers, we actually require
* direct register access to the set and clear registers.
*/
LOG_MODULE_DECLARE(fpga_ice40);
struct fpga_ice40_config_bitbang {
struct gpio_dt_spec clk;
struct gpio_dt_spec pico;
volatile gpio_port_pins_t *set;
volatile gpio_port_pins_t *clear;
uint16_t mhz_delay_count;
const struct pinctrl_dev_config *pincfg;
};
/*
* This is a calibrated delay loop used to achieve a 1 MHz SPI_CLK frequency
* with the GPIO bitbang mode. It is used both in fpga_ice40_send_clocks()
* and fpga_ice40_spi_send_data().
*
* Calibration is achieved via the mhz_delay_count device tree parameter. See
* lattice,ice40-fpga.yaml for details.
*/
static inline void fpga_ice40_delay(size_t n)
{
for (; n > 0; --n) {
__asm__ __volatile__("");
}
}
static void fpga_ice40_send_clocks(size_t delay, volatile gpio_port_pins_t *set,
volatile gpio_port_pins_t *clear, gpio_port_pins_t clk, size_t n)
{
for (; n > 0; --n) {
*clear |= clk;
fpga_ice40_delay(delay);
*set |= clk;
fpga_ice40_delay(delay);
}
}
static void fpga_ice40_spi_send_data(size_t delay, volatile gpio_port_pins_t *set,
volatile gpio_port_pins_t *clear, gpio_port_pins_t cs,
gpio_port_pins_t clk, gpio_port_pins_t pico, uint8_t *z,
size_t n)
{
bool hi;
/* assert chip-select (active low) */
*clear |= cs;
for (; n > 0; --n, ++z) {
/* msb down to lsb */
for (int b = 7; b >= 0; --b) {
/* Data is shifted out on the falling edge (CPOL=0) */
*clear |= clk;
fpga_ice40_delay(delay);
hi = !!(BIT(b) & *z);
if (hi) {
*set |= pico;
} else {
*clear |= pico;
}
/* Data is sampled on the rising edge (CPHA=0) */
*set |= clk;
fpga_ice40_delay(delay);
}
}
/* de-assert chip-select (active low) */
*set |= cs;
}
/*
* See iCE40 Family Handbook, Appendix A. SPI Slave Configuration Procedure,
* pp 15-21.
*
* https://www.latticesemi.com/~/media/LatticeSemi/Documents/Handbooks/iCE40FamilyHandbook.pdf
*/
static int fpga_ice40_load(const struct device *dev, uint32_t *image_ptr, uint32_t img_size)
{
int ret;
uint32_t crc;
gpio_port_pins_t cs;
gpio_port_pins_t clk;
k_spinlock_key_t key;
gpio_port_pins_t pico;
gpio_port_pins_t creset;
struct fpga_ice40_data *data = dev->data;
const struct fpga_ice40_config *config = dev->config;
const struct fpga_ice40_config_bitbang *config_bitbang = config->derived_config;
if (!device_is_ready(config_bitbang->clk.port)) {
LOG_ERR("%s: GPIO for clk is not ready", dev->name);
return -ENODEV;
}
if (!device_is_ready(config_bitbang->pico.port)) {
LOG_ERR("%s: GPIO for pico is not ready", dev->name);
return -ENODEV;
}
/* prepare masks */
cs = BIT(config->bus.config.cs.gpio.pin);
clk = BIT(config_bitbang->clk.pin);
pico = BIT(config_bitbang->pico.pin);
creset = BIT(config->creset.pin);
/* crc check */
crc = crc32_ieee((uint8_t *)image_ptr, img_size);
if (data->loaded && crc == data->crc) {
LOG_WRN("already loaded with image CRC32c: 0x%08x", data->crc);
}
key = k_spin_lock(&data->lock);
/* clear crc */
data->crc = 0;
data->loaded = false;
fpga_ice40_crc_to_str(0, data->info);
LOG_DBG("Initializing GPIO");
ret = gpio_pin_configure_dt(&config->cdone, GPIO_INPUT) ||
gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_HIGH) ||
gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_HIGH) ||
gpio_pin_configure_dt(&config_bitbang->clk, GPIO_OUTPUT_HIGH) ||
gpio_pin_configure_dt(&config_bitbang->pico, GPIO_OUTPUT_HIGH);
__ASSERT(ret == 0, "Failed to initialize GPIO: %d", ret);
LOG_DBG("Set CRESET low");
LOG_DBG("Set SPI_CS low");
*config_bitbang->clear |= (creset | cs);
/* Wait a minimum of 200ns */
LOG_DBG("Delay %u us", config->creset_delay_us);
fpga_ice40_delay(2 * config_bitbang->mhz_delay_count * config->creset_delay_us);
if (gpio_pin_get_dt(&config->cdone) != 0) {
LOG_ERR("CDONE should be low after the reset");
ret = -EIO;
goto unlock;
}
LOG_DBG("Set CRESET high");
*config_bitbang->set |= creset;
LOG_DBG("Delay %u us", config->config_delay_us);
k_busy_wait(config->config_delay_us);
LOG_DBG("Set SPI_CS high");
*config_bitbang->set |= cs;
LOG_DBG("Send %u clocks", config->leading_clocks);
fpga_ice40_send_clocks(config_bitbang->mhz_delay_count, config_bitbang->set,
config_bitbang->clear, clk, config->leading_clocks);
LOG_DBG("Set SPI_CS low");
LOG_DBG("Send bin file");
LOG_DBG("Set SPI_CS high");
fpga_ice40_spi_send_data(config_bitbang->mhz_delay_count, config_bitbang->set,
config_bitbang->clear, cs, clk, pico, (uint8_t *)image_ptr,
img_size);
LOG_DBG("Send %u clocks", config->trailing_clocks);
fpga_ice40_send_clocks(config_bitbang->mhz_delay_count, config_bitbang->set,
config_bitbang->clear, clk, config->trailing_clocks);
LOG_DBG("checking CDONE");
ret = gpio_pin_get_dt(&config->cdone);
if (ret < 0) {
LOG_ERR("failed to read CDONE: %d", ret);
goto unlock;
} else if (ret != 1) {
ret = -EIO;
LOG_ERR("CDONE did not go high");
goto unlock;
}
ret = 0;
data->loaded = true;
fpga_ice40_crc_to_str(crc, data->info);
LOG_INF("Loaded image with CRC32 0x%08x", crc);
unlock:
(void)gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_HIGH);
(void)gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_HIGH);
(void)gpio_pin_configure_dt(&config_bitbang->clk, GPIO_DISCONNECTED);
(void)gpio_pin_configure_dt(&config_bitbang->pico, GPIO_DISCONNECTED);
#ifdef CONFIG_PINCTRL
(void)pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
#endif /* CONFIG_PINCTRL */
k_spin_unlock(&data->lock, key);
return ret;
}
static const struct fpga_driver_api fpga_ice40_api = {
.get_status = fpga_ice40_get_status,
.reset = fpga_ice40_reset,
.load = fpga_ice40_load,
.on = fpga_ice40_on,
.off = fpga_ice40_off,
.get_info = fpga_ice40_get_info,
};
#ifdef CONFIG_PINCTRL
#define FPGA_ICE40_PINCTRL_DEFINE(inst) PINCTRL_DT_DEFINE(DT_INST_PARENT(inst))
#define FPGA_ICE40_PINCTRL_GET(inst) .pincfg = PINCTRL_DT_DEV_CONFIG_GET(DT_INST_PARENT(inst)),
#else
#define FPGA_ICE40_PINCTRL_DEFINE(inst)
#define FPGA_ICE40_PINCTRL_GET(inst)
#endif /* CONFIG_PINCTRL */
#define FPGA_ICE40_DEFINE(inst) \
BUILD_ASSERT(DT_INST_PROP(inst, mhz_delay_count) >= 0); \
\
FPGA_ICE40_PINCTRL_DEFINE(inst); \
static struct fpga_ice40_data fpga_ice40_data_##inst; \
\
static const struct fpga_ice40_config_bitbang fpga_ice40_config_bitbang_##inst = { \
.clk = GPIO_DT_SPEC_INST_GET(inst, clk_gpios), \
.pico = GPIO_DT_SPEC_INST_GET(inst, pico_gpios), \
.set = DT_INST_PROP(inst, gpios_set_reg), \
.clear = DT_INST_PROP(inst, gpios_clear_reg), \
.mhz_delay_count = DT_INST_PROP(inst, mhz_delay_count), \
FPGA_ICE40_PINCTRL_GET(inst)}; \
\
FPGA_ICE40_CONFIG_DEFINE(inst, &fpga_ice40_config_bitbang_##inst); \
\
DEVICE_DT_INST_DEFINE(inst, fpga_ice40_init, NULL, &fpga_ice40_data_##inst, \
&fpga_ice40_config_##inst, POST_KERNEL, CONFIG_FPGA_INIT_PRIORITY, \
&fpga_ice40_api);
#define DT_DRV_COMPAT lattice_ice40_fpga_bitbang
DT_INST_FOREACH_STATUS_OKAY(FPGA_ICE40_DEFINE)

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2022 Meta
* Copyright (c) 2024 SILA Embedded Solutions GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "fpga_ice40_common.h"
LOG_MODULE_REGISTER(fpga_ice40);
void fpga_ice40_crc_to_str(uint32_t crc, char *s)
{
char ch;
uint8_t i;
uint8_t nibble;
const char *table = "0123456789abcdef";
for (i = 0; i < sizeof(crc) * NIBBLES_PER_BYTE; ++i, crc >>= BITS_PER_NIBBLE) {
nibble = crc & GENMASK(BITS_PER_NIBBLE, 0);
ch = table[nibble];
s[sizeof(crc) * NIBBLES_PER_BYTE - i - 1] = ch;
}
s[sizeof(crc) * NIBBLES_PER_BYTE] = '\0';
}
enum FPGA_status fpga_ice40_get_status(const struct device *dev)
{
enum FPGA_status st;
k_spinlock_key_t key;
struct fpga_ice40_data *data = dev->data;
key = k_spin_lock(&data->lock);
if (data->loaded && data->on) {
st = FPGA_STATUS_ACTIVE;
} else {
st = FPGA_STATUS_INACTIVE;
}
k_spin_unlock(&data->lock, key);
return st;
}
static int fpga_ice40_on_off(const struct device *dev, bool on)
{
int ret;
k_spinlock_key_t key;
struct fpga_ice40_data *data = dev->data;
const struct fpga_ice40_config *config = dev->config;
key = k_spin_lock(&data->lock);
ret = gpio_pin_configure_dt(&config->creset, on ? GPIO_OUTPUT_HIGH : GPIO_OUTPUT_LOW);
if (ret < 0) {
goto unlock;
}
data->on = on;
ret = 0;
unlock:
k_spin_unlock(&data->lock, key);
return ret;
}
int fpga_ice40_on(const struct device *dev)
{
return fpga_ice40_on_off(dev, true);
}
int fpga_ice40_off(const struct device *dev)
{
return fpga_ice40_on_off(dev, false);
}
int fpga_ice40_reset(const struct device *dev)
{
return fpga_ice40_off(dev) || fpga_ice40_on(dev);
}
const char *fpga_ice40_get_info(const struct device *dev)
{
struct fpga_ice40_data *data = dev->data;
return data->info;
}
int fpga_ice40_init(const struct device *dev)
{
int ret;
const struct fpga_ice40_config *config = dev->config;
if (!device_is_ready(config->creset.port)) {
LOG_ERR("%s: GPIO for creset is not ready", dev->name);
return -ENODEV;
}
if (!device_is_ready(config->cdone.port)) {
LOG_ERR("%s: GPIO for cdone is not ready", dev->name);
return -ENODEV;
}
ret = gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_HIGH);
if (ret < 0) {
LOG_ERR("failed to configure CRESET: %d", ret);
return ret;
}
ret = gpio_pin_configure_dt(&config->cdone, GPIO_INPUT);
if (ret < 0) {
LOG_ERR("Failed to initialize CDONE: %d", ret);
return ret;
}
return 0;
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (c) 2022 Meta
* Copyright (c) 2024 SILA Embedded Solutions GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_SUBSYS_FPGA_FPGA_ICE40_COMMON_H_
#define ZEPHYR_SUBSYS_FPGA_FPGA_ICE40_COMMON_H_
#include <stdbool.h>
#include <stdint.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/fpga.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
/*
* Values in Hz, intentionally to be comparable with the spi-max-frequency
* property from DT bindings in spi-device.yaml.
*/
#define FPGA_ICE40_SPI_HZ_MIN 1000000
#define FPGA_ICE40_SPI_HZ_MAX 25000000
#define FPGA_ICE40_CRESET_DELAY_US_MIN 1 /* 200ns absolute minimum */
#define FPGA_ICE40_CONFIG_DELAY_US_MIN 1200
#define FPGA_ICE40_LEADING_CLOCKS_MIN 8
#define FPGA_ICE40_TRAILING_CLOCKS_MIN 49
#define FPGA_ICE40_CONFIG_DEFINE(inst, derived_config_) \
BUILD_ASSERT(DT_INST_PROP(inst, spi_max_frequency) >= FPGA_ICE40_SPI_HZ_MIN); \
BUILD_ASSERT(DT_INST_PROP(inst, spi_max_frequency) <= FPGA_ICE40_SPI_HZ_MAX); \
BUILD_ASSERT(DT_INST_PROP(inst, config_delay_us) >= FPGA_ICE40_CONFIG_DELAY_US_MIN); \
BUILD_ASSERT(DT_INST_PROP(inst, config_delay_us) <= UINT16_MAX); \
BUILD_ASSERT(DT_INST_PROP(inst, creset_delay_us) >= FPGA_ICE40_CRESET_DELAY_US_MIN); \
BUILD_ASSERT(DT_INST_PROP(inst, creset_delay_us) <= UINT16_MAX); \
BUILD_ASSERT(DT_INST_PROP(inst, leading_clocks) >= FPGA_ICE40_LEADING_CLOCKS_MIN); \
BUILD_ASSERT(DT_INST_PROP(inst, leading_clocks) <= UINT8_MAX); \
BUILD_ASSERT(DT_INST_PROP(inst, trailing_clocks) >= FPGA_ICE40_TRAILING_CLOCKS_MIN); \
BUILD_ASSERT(DT_INST_PROP(inst, trailing_clocks) <= UINT8_MAX); \
\
static const struct fpga_ice40_config fpga_ice40_config_##inst = { \
.bus = SPI_DT_SPEC_INST_GET(inst, \
SPI_OP_MODE_MASTER | SPI_MODE_CPOL | SPI_MODE_CPHA | \
SPI_WORD_SET(8) | SPI_TRANSFER_MSB, \
0), \
.creset = GPIO_DT_SPEC_INST_GET(inst, creset_gpios), \
.cdone = GPIO_DT_SPEC_INST_GET(inst, cdone_gpios), \
.config_delay_us = DT_INST_PROP(inst, config_delay_us), \
.creset_delay_us = DT_INST_PROP(inst, creset_delay_us), \
.leading_clocks = DT_INST_PROP(inst, leading_clocks), \
.trailing_clocks = DT_INST_PROP(inst, trailing_clocks), \
.derived_config = derived_config_, \
}
struct fpga_ice40_data {
uint32_t crc;
/* simply use crc32 as info */
char info[2 * sizeof(uint32_t) + 1];
bool on;
bool loaded;
struct k_spinlock lock;
};
struct fpga_ice40_config {
struct spi_dt_spec bus;
struct gpio_dt_spec cdone;
struct gpio_dt_spec creset;
uint16_t creset_delay_us;
uint16_t config_delay_us;
uint8_t leading_clocks;
uint8_t trailing_clocks;
const void *derived_config;
};
void fpga_ice40_crc_to_str(uint32_t crc, char *s);
enum FPGA_status fpga_ice40_get_status(const struct device *dev);
int fpga_ice40_on(const struct device *dev);
int fpga_ice40_off(const struct device *dev);
int fpga_ice40_reset(const struct device *dev);
const char *fpga_ice40_get_info(const struct device *dev);
int fpga_ice40_init(const struct device *dev);
#endif /* ZEPHYR_SUBSYS_FPGA_FPGA_ICE40_COMMON_H_ */

View file

@ -0,0 +1,188 @@
/*
* Copyright (c) 2022 Meta
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/device.h>
#include <zephyr/drivers/fpga.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/crc.h>
#include <zephyr/sys/util.h>
#include "fpga_ice40_common.h"
LOG_MODULE_DECLARE(fpga_ice40);
static int fpga_ice40_load(const struct device *dev, uint32_t *image_ptr, uint32_t img_size)
{
int ret;
uint32_t crc;
k_spinlock_key_t key;
struct spi_buf tx_buf;
const struct spi_buf_set tx_bufs = {
.buffers = &tx_buf,
.count = 1,
};
struct fpga_ice40_data *data = dev->data;
uint8_t clock_buf[(UINT8_MAX + 1) / BITS_PER_BYTE];
const struct fpga_ice40_config *config = dev->config;
struct spi_dt_spec bus;
memcpy(&bus, &config->bus, sizeof(bus));
/*
* Disable the automatism for chip select within the SPI driver,
* as the configuration sequence requires this signal to be inactive
* during the leading and trailing clock phase.
*/
bus.config.cs.gpio.port = NULL;
/* crc check */
crc = crc32_ieee((uint8_t *)image_ptr, img_size);
if (data->loaded && crc == data->crc) {
LOG_WRN("already loaded with image CRC32c: 0x%08x", data->crc);
}
key = k_spin_lock(&data->lock);
/* clear crc */
data->crc = 0;
data->loaded = false;
fpga_ice40_crc_to_str(0, data->info);
LOG_DBG("Initializing GPIO");
ret = gpio_pin_configure_dt(&config->cdone, GPIO_INPUT) ||
gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_HIGH) ||
gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_HIGH);
__ASSERT(ret == 0, "Failed to initialize GPIO: %d", ret);
LOG_DBG("Set CRESET low");
ret = gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_LOW);
if (ret < 0) {
LOG_ERR("failed to set CRESET low: %d", ret);
goto unlock;
}
LOG_DBG("Set SPI_CS low");
ret = gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_LOW);
if (ret < 0) {
LOG_ERR("failed to set SPI_CS low: %d", ret);
goto unlock;
}
/* Wait a minimum of 200ns */
LOG_DBG("Delay %u us", config->creset_delay_us);
k_usleep(config->creset_delay_us);
if (gpio_pin_get_dt(&config->cdone) != 0) {
LOG_ERR("CDONE should be low after the reset");
ret = -EIO;
goto unlock;
}
LOG_DBG("Set CRESET high");
ret = gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_HIGH);
if (ret < 0) {
LOG_ERR("failed to set CRESET high: %d", ret);
goto unlock;
}
LOG_DBG("Delay %u us", config->config_delay_us);
k_busy_wait(config->config_delay_us);
LOG_DBG("Set SPI_CS high");
ret = gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_HIGH);
if (ret < 0) {
LOG_ERR("failed to set SPI_CS high: %d", ret);
goto unlock;
}
LOG_DBG("Send %u clocks", config->leading_clocks);
tx_buf.buf = clock_buf;
tx_buf.len = DIV_ROUND_UP(config->leading_clocks, BITS_PER_BYTE);
ret = spi_write_dt(&bus, &tx_bufs);
if (ret < 0) {
LOG_ERR("Failed to send leading %u clocks: %d", config->leading_clocks, ret);
goto unlock;
}
LOG_DBG("Set SPI_CS low");
ret = gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_LOW);
if (ret < 0) {
LOG_ERR("failed to set SPI_CS low: %d", ret);
goto unlock;
}
LOG_DBG("Send bin file");
tx_buf.buf = image_ptr;
tx_buf.len = img_size;
ret = spi_write_dt(&bus, &tx_bufs);
if (ret < 0) {
LOG_ERR("Failed to send bin file: %d", ret);
goto unlock;
}
LOG_DBG("Set SPI_CS high");
ret = gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_HIGH);
if (ret < 0) {
LOG_ERR("failed to set SPI_CS high: %d", ret);
goto unlock;
}
LOG_DBG("Send %u clocks", config->trailing_clocks);
tx_buf.buf = clock_buf;
tx_buf.len = DIV_ROUND_UP(config->trailing_clocks, BITS_PER_BYTE);
ret = spi_write_dt(&bus, &tx_bufs);
if (ret < 0) {
LOG_ERR("Failed to send trailing %u clocks: %d", config->trailing_clocks, ret);
goto unlock;
}
LOG_DBG("checking CDONE");
ret = gpio_pin_get_dt(&config->cdone);
if (ret < 0) {
LOG_ERR("failed to read CDONE: %d", ret);
goto unlock;
} else if (ret != 1) {
ret = -EIO;
LOG_ERR("CDONE did not go high");
goto unlock;
}
ret = 0;
data->loaded = true;
fpga_ice40_crc_to_str(crc, data->info);
LOG_INF("Loaded image with CRC32 0x%08x", crc);
unlock:
(void)gpio_pin_configure_dt(&config->creset, GPIO_OUTPUT_HIGH);
(void)gpio_pin_configure_dt(&config->bus.config.cs.gpio, GPIO_OUTPUT_HIGH);
k_spin_unlock(&data->lock, key);
return ret;
}
static const struct fpga_driver_api fpga_ice40_api = {
.get_status = fpga_ice40_get_status,
.reset = fpga_ice40_reset,
.load = fpga_ice40_load,
.on = fpga_ice40_on,
.off = fpga_ice40_off,
.get_info = fpga_ice40_get_info,
};
#define FPGA_ICE40_DEFINE(inst) \
static struct fpga_ice40_data fpga_ice40_data_##inst; \
\
FPGA_ICE40_CONFIG_DEFINE(inst, NULL); \
\
DEVICE_DT_INST_DEFINE(inst, fpga_ice40_init, NULL, &fpga_ice40_data_##inst, \
&fpga_ice40_config_##inst, POST_KERNEL, CONFIG_FPGA_INIT_PRIORITY, \
&fpga_ice40_api);
#define DT_DRV_COMPAT lattice_ice40_fpga
DT_INST_FOREACH_STATUS_OKAY(FPGA_ICE40_DEFINE)

View file

@ -0,0 +1,49 @@
# Copyright (c) 2022 Meta
# SPDX-License-Identifier: Apache-2.0
description: Lattice iCE40 FPGA base
compatible: "lattice,ice40-fpga-base"
include: spi-device.yaml
properties:
cdone-gpios:
type: phandle-array
required: true
description: |
Configuration Done output from iCE40.
Example usage:
cdone-gpios = <&gpio0 0 0>;
creset-gpios:
type: phandle-array
required: true
description: |
Configuration Reset input on iCE40.
Example usage:
creset-gpios = <&gpio0 1 GPIO_PUSH_PULL);
creset-delay-us:
type: int
default: 1
description: |
Delay (in microseconds) between asserting CRESET_B and releasing CRESET_B.
The datasheet specifies a minimum of 200ns, therefore the default is set
to 1us.
config-delay-us:
type: int
default: 1200
description: |
Delay (in microseconds) after releasing CRESET_B to clear internal configuration memory.
The datasheet specifies a minimum of 1200us, which is the default.
leading-clocks:
type: int
default: 8
description: |
Prior to sending the bitstream, issue this number of leading clocks with SPI_CS pulled high.
The datasheet specifies 8 dummy cycles, which is the default.
trailing-clocks:
type: int
default: 49
description: |
After sending the bitstream, issue this number of trailing clocks with SPI_CS pulled high.
The datasheet specifies 49 dummy cycles, which is the default.

View file

@ -0,0 +1,52 @@
# Copyright (c) 2022 Meta
# Copyright (c) 2024 SILA Embedded Solutions GmbH
# SPDX-License-Identifier: Apache-2.0
description: Lattice iCE40 FPGA GPIO bitbang based driver
compatible: "lattice,ice40-fpga-bitbang"
include: lattice,ice40-fpga-base.yaml
properties:
clk-gpios:
type: phandle-array
required: true
description: |
SPI Clock GPIO input on iCE40.
Example usage:
clk-gpios = <&gpio0 5 GPIO_PUSH_PULL>;
pico-gpios:
type: phandle-array
required: true
description: |
Peripheral-In Controller-Out GPIO input on iCE40.
Example usage:
pico-gpios = <&gpio0 7 GPIO_PUSH_PULL>;
gpios-set-reg:
type: int
required: true
description: |
Register address for setting a GPIO.
Example usage:
gpios-set-reg = <0x60004008>;
gpios-clear-reg:
type: int
required: true
description: |
Register address for clearing a GPIO.
Example usage:
gpios-clear-reg = <0x6000400c>;
mhz-delay-count:
type: int
default: 0
description: |
in order to create a 1 MHz square wave in the following
process.
while(true) {
*gpios_set_reg |= BIT(n);
for(int i = mhz_delay_count; i > 0; --i);
*gpios_clear_reg |= BIT(n);
for(int i = mhz_delay_count; i > 0; --i);
}
The default disables the delay.

View file

@ -1,94 +1,8 @@
# Copyright (c) 2022 Meta
# SPDX-License-Identifier: Apache-2.0
description: Lattice iCE40 FPGA
description: Lattice iCE40 FPGA SPI based driver
compatible: "lattice,ice40-fpga"
include: spi-device.yaml
properties:
load-mode-bitbang:
type: boolean
description: |
Select the bitbang mode for loading the bitstream into the FPGA.
This is a workaround to meet the timing requirements fo the iCE40
on low-end microcontrollers.
This option requires clk-gpios, pico-gpios, gpios-set-reg, and
gpios-clear-reg to be defined.
cdone-gpios:
type: phandle-array
required: true
description: |
Configuration Done output from iCE40.
Example usage:
cdone-gpios = <&gpio0 0 0>;
creset-gpios:
type: phandle-array
required: true
description: |
Configuration Reset input on iCE40.
Example usage:
creset-gpios = <&gpio0 1 GPIO_PUSH_PULL);
clk-gpios:
type: phandle-array
description: |
SPI Clock GPIO input on iCE40.
Example usage:
clk-gpios = <&gpio0 5 GPIO_PUSH_PULL>;
pico-gpios:
type: phandle-array
description: |
Peripheral-In Controller-Out GPIO input on iCE40.
Example usage:
pico-gpios = <&gpio0 7 GPIO_PUSH_PULL>;
gpios-set-reg:
type: int
description: |
Register address for setting a GPIO.
Example usage:
gpios-set-reg = <0x60004008>;
gpios-clear-reg:
type: int
description: |
Register address for clearing a GPIO.
Example usage:
gpios-clear-reg = <0x6000400c>;
mhz-delay-count:
type: int
description: |
in order to create a 1 MHz square wave in the following
process.
while(true) {
*gpios_set_reg |= BIT(n);
for(int i = mhz_delay_count; i > 0; --i);
*gpios_clear_reg |= BIT(n);
for(int i = mhz_delay_count; i > 0; --i);
}
Example usage / default:
mhz-delay-count = <0>;
creset-delay-us:
type: int
default: 1
description: |
Delay (in microseconds) between asserting CRESET_B and releasing CRESET_B.
The datasheet specifies a minimum of 200ns, therefore the default is set
to 1us.
config-delay-us:
type: int
default: 1200
description: |
Delay (in microseconds) after releasing CRESET_B to clear internal configuration memory.
The datasheet specifies a minimum of 1200us, which is the default.
leading-clocks:
type: int
default: 8
description: |
Prior to sending the bitstream, issue this number of leading clocks with SPI_CS pulled high.
The datasheet specifies 8 dummy cycles, which is the default.
trailing-clocks:
type: int
default: 49
description: |
After sending the bitstream, issue this number of trailing clocks with SPI_CS pulled high.
The datasheet specifies 49 dummy cycles, which is the default.
include: lattice,ice40-fpga-base.yaml

View file

@ -8,12 +8,11 @@
test_spi_fpga_ice40_gpio: ice40@0 {
status = "okay";
compatible = "lattice,ice40-fpga";
compatible = "lattice,ice40-fpga-bitbang";
reg = <0>;
spi-max-frequency = <1000000>;
load-mode-bitbang;
cdone-gpios = <&test_gpio 0 0>;
creset-gpios = <&test_gpio 0 0>;
clk-gpios = <&test_gpio 0 0>;