diff --git a/doc/releases/release-notes-4.1.rst b/doc/releases/release-notes-4.1.rst index 951c8b213ad..f526738c418 100644 --- a/doc/releases/release-notes-4.1.rst +++ b/doc/releases/release-notes-4.1.rst @@ -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 diff --git a/drivers/fpga/CMakeLists.txt b/drivers/fpga/CMakeLists.txt index 2b449446ed5..925a6362f69 100644 --- a/drivers/fpga/CMakeLists.txt +++ b/drivers/fpga/CMakeLists.txt @@ -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) diff --git a/drivers/fpga/Kconfig.ice40 b/drivers/fpga/Kconfig.ice40 index 0758de8b25d..f081cc84342 100644 --- a/drivers/fpga/Kconfig.ice40 +++ b/drivers/fpga/Kconfig.ice40 @@ -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 diff --git a/drivers/fpga/fpga_ice40.c b/drivers/fpga/fpga_ice40.c deleted file mode 100644 index bc251e5c3b1..00000000000 --- a/drivers/fpga/fpga_ice40.c +++ /dev/null @@ -1,626 +0,0 @@ -/* - * Copyright (c) 2022 Meta - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#define DT_DRV_COMPAT lattice_ice40_fpga - -#include -#include - -#include -#include -#include -#ifdef CONFIG_PINCTRL -#include -#endif -#include -#include -#include -#include -#include - -/* - * 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) diff --git a/drivers/fpga/fpga_ice40_bitbang.c b/drivers/fpga/fpga_ice40_bitbang.c new file mode 100644 index 00000000000..941efffc8ae --- /dev/null +++ b/drivers/fpga/fpga_ice40_bitbang.c @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2022 Meta + * Copyright (c) 2024 SILA Embedded Solutions GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#ifdef CONFIG_PINCTRL +#include +#endif /* CONFIG_PINCTRL */ +#include +#include +#include +#include + +#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) diff --git a/drivers/fpga/fpga_ice40_common.c b/drivers/fpga/fpga_ice40_common.c new file mode 100644 index 00000000000..588c1cb7c62 --- /dev/null +++ b/drivers/fpga/fpga_ice40_common.c @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2022 Meta + * Copyright (c) 2024 SILA Embedded Solutions GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#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; +} diff --git a/drivers/fpga/fpga_ice40_common.h b/drivers/fpga/fpga_ice40_common.h new file mode 100644 index 00000000000..55faf0eef56 --- /dev/null +++ b/drivers/fpga/fpga_ice40_common.h @@ -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 +#include +#include +#include +#include +#include + +/* + * 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_ */ diff --git a/drivers/fpga/fpga_ice40_spi.c b/drivers/fpga/fpga_ice40_spi.c new file mode 100644 index 00000000000..10d2a8b7129 --- /dev/null +++ b/drivers/fpga/fpga_ice40_spi.c @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2022 Meta + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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) diff --git a/dts/bindings/fpga/lattice,ice40-fpga-base.yaml b/dts/bindings/fpga/lattice,ice40-fpga-base.yaml new file mode 100644 index 00000000000..8766e6d63ae --- /dev/null +++ b/dts/bindings/fpga/lattice,ice40-fpga-base.yaml @@ -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. diff --git a/dts/bindings/fpga/lattice,ice40-fpga-bitbang.yaml b/dts/bindings/fpga/lattice,ice40-fpga-bitbang.yaml new file mode 100644 index 00000000000..bb6356bf7d0 --- /dev/null +++ b/dts/bindings/fpga/lattice,ice40-fpga-bitbang.yaml @@ -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. diff --git a/dts/bindings/fpga/lattice,ice40-fpga.yaml b/dts/bindings/fpga/lattice,ice40-fpga.yaml index 6355092f600..59db412ab1e 100644 --- a/dts/bindings/fpga/lattice,ice40-fpga.yaml +++ b/dts/bindings/fpga/lattice,ice40-fpga.yaml @@ -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 diff --git a/tests/drivers/build_all/fpga/spi.dtsi b/tests/drivers/build_all/fpga/spi.dtsi index c1d5c279654..bf110cefb80 100644 --- a/tests/drivers/build_all/fpga/spi.dtsi +++ b/tests/drivers/build_all/fpga/spi.dtsi @@ -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>;