diff --git a/app/commands.hh b/app/commands.hh index bf23ca5..86bfd80 100644 --- a/app/commands.hh +++ b/app/commands.hh @@ -5,6 +5,7 @@ #pragma once #include "flash/flash.hh" +#include "embeddedpp/gpio.hh" #include "visuals/progressbar.hh" #include "visuals/throughput.hh" #include @@ -262,4 +263,23 @@ struct LoadFile : public Commands { } }; +template + requires embeddedpp::Gpio +struct GpioWrite { + T& gpio; + uint8_t pin; + bool value; + + GpioWrite(T& gpio, uint8_t pin, bool value) : gpio(gpio), pin(pin), value(value) {} + + int run() { + if (is_error(gpio.set_pin(pin, value))) { + std::println("GPIO write failed"); + return 1; + } + std::println("GPIO{} = {}", pin, value ? 1 : 0); + return 0; + } +}; + } // namespace commands diff --git a/app/main.cc b/app/main.cc index 046d468..dd73453 100644 --- a/app/main.cc +++ b/app/main.cc @@ -12,6 +12,7 @@ #include "ftdi/ftdi.hh" #include "ftdi/spi_host.hh" +#include "ftdi/gpio.hh" #include "flash/flash.hh" #include "commands.hh" #include "ftdi/log.hh" @@ -41,10 +42,9 @@ static std::vector scan() { } static std::unique_ptr -new_flash_command(const std::string& name, const std::string& desc) { +new_command(const std::string& name, const std::string& desc) { auto cmd = std::make_unique(name); cmd->add_description(desc); - cmd->add_argument("--interface", "-i").help("One of [spi, i2c, gpio]"); cmd->add_argument("--traces", "-t") .help("Enable ftdi traces") .default_value(false) @@ -52,6 +52,12 @@ new_flash_command(const std::string& name, const std::string& desc) { cmd->add_argument("--ftdi") .default_value(std::string("FT4222")) .help("Filter ftdi chips connected to USB"); + return cmd; +} + +static std::unique_ptr +new_flash_command(const std::string& name, const std::string& desc) { + auto cmd = new_command(name, desc); cmd->add_argument("--clock") .default_value(std::size_t{15000000}) .help("The spi clock speed in Hz.") @@ -79,6 +85,22 @@ handle_flash_command(std::unique_ptr& cmd) { exit(0); } +static std::optional +handle_gpio_command(std::unique_ptr& cmd) { + auto ftdi_filter = cmd->get("--ftdi"); + auto devices = scan(); + auto filtered = ftdi::DeviceInfo::filter_by_description(devices, ftdi_filter); + if (filtered.empty()) { + std::print("No {} ftdi found.\n", ftdi_filter); + exit(0); + } + if (auto opt = ftdi::Gpio::from_device_info(filtered[0])) { + return opt; + } + std::println("Can't open GPIO."); + exit(0); +} + int main(int argc, char* argv[]) { std::map commands; @@ -204,6 +226,19 @@ int main(int argc, char* argv[]) { return 0; }; + auto gpio_write_cmd = new_command("gpio-write", "Write a value to an FTDI GPIO pin."); + gpio_write_cmd->add_argument("pin").help("GPIO pin number (0-3)").required().scan<'d', int>(); + gpio_write_cmd->add_argument("value").help("Value to write (0 or 1)").required().scan<'d', int>(); + program.add_subparser(*gpio_write_cmd); + commands["gpio-write"] = [&]() -> int { + auto pin = static_cast(gpio_write_cmd->get("pin")); + auto value = gpio_write_cmd->get("value") != 0; + auto gpio = handle_gpio_command(gpio_write_cmd); + commands::GpioWrite(*gpio, pin, value).run(); + gpio->close(); + return 0; + }; + if (argc == 1) { std::cout << program; // This prints the auto-generated help return 0; diff --git a/lib/embeddedpp/CMakeLists.txt b/lib/embeddedpp/CMakeLists.txt index f2185d2..84d14da 100644 --- a/lib/embeddedpp/CMakeLists.txt +++ b/lib/embeddedpp/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources(embeddedpp BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} FILES spi.hh + gpio.hh helpers.hh ) diff --git a/lib/embeddedpp/gpio.hh b/lib/embeddedpp/gpio.hh new file mode 100644 index 0000000..b81af9b --- /dev/null +++ b/lib/embeddedpp/gpio.hh @@ -0,0 +1,19 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include "helpers.hh" + +namespace embeddedpp { + +template +concept Gpio = requires(T t, uint8_t pin, bool value) { + { t.set_pin(pin, value) } -> std::same_as; + { t.get_pin(pin) } -> std::same_as>; +}; + +} // namespace embeddedpp diff --git a/lib/ftdi/CMakeLists.txt b/lib/ftdi/CMakeLists.txt index 0438cf3..a674556 100644 --- a/lib/ftdi/CMakeLists.txt +++ b/lib/ftdi/CMakeLists.txt @@ -7,12 +7,14 @@ add_library(ftdipp) target_sources(ftdipp PRIVATE spi_host.cc + gpio.cc PUBLIC FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} FILES ftdi.hh spi_host.hh + gpio.hh log.hh ) diff --git a/lib/ftdi/gpio.cc b/lib/ftdi/gpio.cc new file mode 100644 index 0000000..2454ce1 --- /dev/null +++ b/lib/ftdi/gpio.cc @@ -0,0 +1,150 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "gpio.hh" +#include "libft4222.h" +#include "libmpsse_spi.h" +#include "ftd2xx.h" +#include +#include +#include + +namespace ftdi { + +void Gpio::close() { + if (mpsse) { + SPI_CloseChannel(handle); + Cleanup_libMPSSE(); + } else { + FT4222_UnInitialize(handle); + FT_Close(handle); + } +} + +embeddedpp::Result Gpio::get_pin(uint8_t pin) { + uint8_t mask = static_cast(1 << pin); + if (mpsse) { + if (pin > 7) { + std::cerr << std::format("gpio pin must be 0-7\n"); + return embeddedpp::Code::InvalidArgument; + } + uint8_t value = 0; + FT_STATUS status = FT_ReadGPIO(handle, &value); + return (value & mask) == mask; + } + + if (pin > 3) { + std::cerr << std::format("gpio pin must be 0-3\n"); + return embeddedpp::Code::InvalidArgument; + } + + GPIO_Port port = static_cast(pin); + BOOL enable; + FT4222_STATUS status = FT4222_GPIO_Read(handle, port, &enable); + if (FT4222_OK != status) { + std::cerr << std::format("FT4222_GPIO_Read: {}\n", status); + return embeddedpp::Code::Generic; + } + return enable > 0; +} + +embeddedpp::Status Gpio::set_pin(uint8_t pin, bool en) { + if (mpsse) { + if (pin > 7) { + std::cerr << std::format("gpio pin must be 0-7\n"); + return embeddedpp::Code::InvalidArgument; + } + uint8_t value = 0; + FT_STATUS status; + status = FT_ReadGPIO(handle, &value); + if (en) { + value |= static_cast(1 << pin); + } else { + value &= static_cast(~(1 << pin)); + } + status = FT_WriteGPIO(handle, 0xff, value); + if (FT_OK != status) { + std::cerr << std::format("FT_WriteGPIO: {}\n", status); + return embeddedpp::Code::Generic; + } + return true; + } + + if (pin > 3) { + std::cerr << std::format("gpio pin must be 0-3\n"); + return embeddedpp::Code::InvalidArgument; + } + + GPIO_Port port = static_cast(pin); + FT4222_STATUS status = FT4222_GPIO_Write(handle, port, en ? TRUE : FALSE); + if (FT4222_OK != status) { + std::cerr << std::format("FT4222_GPIO_Write: {}\n", status); + return embeddedpp::Code::Generic; + } + return embeddedpp::Code::Ok; +} + +static std::optional new_ft4222_gpio(DeviceInfo& device) { + FT_HANDLE handle; + FT_STATUS res = FT_OpenEx((PVOID)(uintptr_t)device.loc_id, FT_OPEN_BY_LOCATION, &handle); + if (res != FT_OK) { + std::cerr << std::format("Gpio Open:{}\n", res); + return std::nullopt; + } + + // Initialize all four GPIO pins as outputs. + GPIO_Dir dir[4] = {GPIO_OUTPUT, GPIO_OUTPUT, GPIO_OUTPUT, GPIO_OUTPUT}; + FT4222_STATUS status = FT4222_GPIO_Init(handle, dir); + if (FT4222_OK != status) { + std::cerr << std::format("FT4222_GPIO_Init:{}\n", status); + FT_Close(handle); + return std::nullopt; + } + + return std::optional{handle}; +} + +static std::optional new_ft2232_gpio(DeviceInfo& device) { + FT_HANDLE handle; + FT_STATUS res; + const uint32_t channel = 0; + Init_libMPSSE(); + + std::println("Opening {}, channel {}", device.serial_number, channel); + res = SPI_OpenChannel(channel, &handle); + if (res != FT_OK) { + std::cerr << std::format("Gpio Open:{}\n", res); + Cleanup_libMPSSE(); + return std::nullopt; + } + + ChannelConfig config = {.ClockRate = 1000000, // 1MHz + .LatencyTimer = 2, + .configOptions = SPI_CONFIG_OPTION_MODE0 | SPI_CONFIG_OPTION_CS_DBUS3 | + SPI_CONFIG_OPTION_CS_ACTIVELOW}; + res = SPI_InitChannel(handle, &config); + if (res != FT_OK) { + std::cerr << std::format("Spi InitChannel: {}\n", res); + return std::nullopt; + } + + return std::optional{Gpio(handle, true)}; +} + +std::optional Gpio::from_device_info(DeviceInfo& device) { + switch (device.type) { + case DeviceType::Ftdi_2232h: + return new_ft2232_gpio(device); + case DeviceType::Ftdi_4222h_0: + case DeviceType::Ftdi_4222h_1_2: + case DeviceType::Ftdi_4222h_3: + return new_ft4222_gpio(device); + default: + break; + } + std::cerr << std::format("Gpio: device not supported {}\n", device); + return std::nullopt; +} + +}; // namespace ftdi diff --git a/lib/ftdi/gpio.hh b/lib/ftdi/gpio.hh new file mode 100644 index 0000000..4aa65ce --- /dev/null +++ b/lib/ftdi/gpio.hh @@ -0,0 +1,32 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include "libft4222.h" +#include "ftdi.hh" +#include "embeddedpp/helpers.hh" + +namespace ftdi { + +class Gpio { + FT_HANDLE handle; + bool mpsse = false; + + public: + explicit Gpio(FT_HANDLE handle) noexcept : handle(handle) {} + Gpio(FT_HANDLE handle, bool mpsse) noexcept : handle(handle), mpsse(mpsse) {} + ~Gpio() = default; + + void close(); + + embeddedpp::Status set_pin(uint8_t pin, bool value); + embeddedpp::Result get_pin(uint8_t pin); + + static std::optional from_device_info(DeviceInfo& device); +}; + +} // namespace ftdi