Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions app/commands.hh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include "flash/flash.hh"
#include "embeddedpp/gpio.hh"
#include "visuals/progressbar.hh"
#include "visuals/throughput.hh"
#include <fstream>
Expand Down Expand Up @@ -262,4 +263,23 @@ struct LoadFile : public Commands<T> {
}
};

template <typename T>
requires embeddedpp::Gpio<T>
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
39 changes: 37 additions & 2 deletions app/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -41,17 +42,22 @@ static std::vector<ftdi::DeviceInfo> scan() {
}

static std::unique_ptr<argparse::ArgumentParser>
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<argparse::ArgumentParser>(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)
.implicit_value(true);
cmd->add_argument("--ftdi")
.default_value(std::string("FT4222"))
.help("Filter ftdi chips connected to USB");
return cmd;
}

static std::unique_ptr<argparse::ArgumentParser>
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.")
Expand Down Expand Up @@ -79,6 +85,22 @@ handle_flash_command(std::unique_ptr<argparse::ArgumentParser>& cmd) {
exit(0);
}

static std::optional<ftdi::Gpio>
handle_gpio_command(std::unique_ptr<argparse::ArgumentParser>& cmd) {
auto ftdi_filter = cmd->get<std::string>("--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<std::string, Action> commands;

Expand Down Expand Up @@ -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<uint8_t>(gpio_write_cmd->get<int>("pin"));
auto value = gpio_write_cmd->get<int>("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;
Expand Down
1 change: 1 addition & 0 deletions lib/embeddedpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ target_sources(embeddedpp
BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
FILES
spi.hh
gpio.hh
helpers.hh
)

Expand Down
19 changes: 19 additions & 0 deletions lib/embeddedpp/gpio.hh
Original file line number Diff line number Diff line change
@@ -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 <concepts>
#include <cstdint>
#include "helpers.hh"

namespace embeddedpp {

template <typename T>
concept Gpio = requires(T t, uint8_t pin, bool value) {
{ t.set_pin(pin, value) } -> std::same_as<Status>;
{ t.get_pin(pin) } -> std::same_as<Result<bool>>;
};

} // namespace embeddedpp
2 changes: 2 additions & 0 deletions lib/ftdi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
150 changes: 150 additions & 0 deletions lib/ftdi/gpio.cc
Original file line number Diff line number Diff line change
@@ -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 <iostream>
#include <format>
#include <print>

namespace ftdi {

void Gpio::close() {
if (mpsse) {
SPI_CloseChannel(handle);
Cleanup_libMPSSE();
} else {
FT4222_UnInitialize(handle);
FT_Close(handle);
}
}

embeddedpp::Result<bool> Gpio::get_pin(uint8_t pin) {
uint8_t mask = static_cast<uint8_t>(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<GPIO_Port>(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<uint8_t>(1 << pin);
} else {
value &= static_cast<uint8_t>(~(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<GPIO_Port>(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<Gpio> 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<Gpio>{handle};
}

static std::optional<Gpio> 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>{Gpio(handle, true)};
}

std::optional<Gpio> 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
32 changes: 32 additions & 0 deletions lib/ftdi/gpio.hh
Original file line number Diff line number Diff line change
@@ -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 <cstdint>
#include <optional>
#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<bool> get_pin(uint8_t pin);

static std::optional<Gpio> from_device_info(DeviceInfo& device);
};

} // namespace ftdi