blob: 8fbe25977bd03daed20b41d63849f9c72028d1f4 [file] [log] [blame]
#include "tlbmc/hal/gpio/gpio.h"
#include <gpiod.h>
#include <memory>
#include <string>
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "g3/macros.h"
#include "gpio_config.pb.h"
namespace milotic_tlbmc {
namespace internal {
absl::StatusOr<int> ParseGpioRequestType(const GpioConfig& config) {
switch (config.request_type_case()) {
case GpioConfig::kGpioDirection:
return config.gpio_direction() == GPIO_DIRECTION_INPUT
? GPIOD_LINE_REQUEST_DIRECTION_INPUT
: GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
case GpioConfig::kGpioEventConfig:
switch (config.gpio_event_config().event_type()) {
case GPIO_EVENT_RISING:
return GPIOD_LINE_REQUEST_EVENT_RISING_EDGE;
case GPIO_EVENT_FALLING:
return GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE;
case GPIO_EVENT_BOTH:
return GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
default:
return absl::InvalidArgumentError("Unsupported GPIO event type");
}
default:
return absl::InvalidArgumentError("Unsupported GPIO request type");
}
}
} // namespace internal
absl::StatusOr<bool> Gpio::GetValue() const {
if (config_.gpio_direction() == GPIO_DIRECTION_OUTPUT) {
return absl::FailedPreconditionError(
"GPIO line in OUTPUT direction cannot be read");
}
int value = gpiod_line_get_value(gpio_line_.get());
if (value < 0) {
return absl::InternalError("Failed to get GPIO line value");
}
if (value == 1) {
return true;
}
if (value == 0) {
return false;
}
// This should not happen.
return absl::InternalError("Invalid GPIO line value");
}
absl::Status Gpio::SetValue(bool value) {
if (config_.gpio_direction() != GPIO_DIRECTION_OUTPUT) {
return absl::FailedPreconditionError(
"GPIO line is not in OUTPUT direction");
}
int write_value = value ? 1 : 0;
if (gpiod_line_set_value(gpio_line_.get(), write_value) < 0) {
return absl::InternalError("Failed to set GPIO line value");
}
return absl::OkStatus();
}
std::string Gpio::GetGpioName() const {
if (config_.identifier_case() == GpioConfig::kGpioIdentifier) {
return absl::StrCat(config_.gpio_identifier().chip_id(), ":",
config_.gpio_identifier().gpio_num());
}
if (config_.identifier_case() == GpioConfig::kLineName) {
return config_.line_name();
}
// This should not happen.
return "Unknown GPIO name";
}
absl::StatusOr<std::unique_ptr<Gpio>> Gpio::Create(const GpioConfig& config) {
gpiod_line* line = nullptr;
// If chip_id and gpio_num are provided, we can use the (chip_id, gpio_num) to
// get the GPIO line. Otherwise, we use the line_name to get the GPIO line.
if (config.identifier_case() == GpioConfig::kGpioIdentifier) {
line = gpiod_line_get(config.gpio_identifier().chip_id().c_str(),
config.gpio_identifier().gpio_num());
} else if (config.identifier_case() == GpioConfig::kLineName) {
line = gpiod_line_find(config.line_name().c_str());
} else {
return absl::InvalidArgumentError(absl::StrCat(
"Unsupported GPIO identifier: ", config.identifier_case()));
}
if (line == nullptr) {
return absl::InternalError(
absl::StrCat("Failed to find GPIO line: ", config.line_name()));
}
gpiod_line_request_config request_config{
.consumer = "tlbmc-gpio",
.flags = 0,
};
ECCLESIA_ASSIGN_OR_RETURN(auto request_type,
internal::ParseGpioRequestType(config));
request_config.request_type = request_type;
if (config.request_type_case() == GpioConfig::kGpioDirection &&
config.gpio_direction() == GPIO_DIRECTION_OUTPUT) {
if (config.gpio_polarity() == GPIO_POLARITY_UNKNOWN) {
return absl::InvalidArgumentError(absl::StrCat(
"Output GPIO line ", config.line_name(), " has unknown polarity"));
}
// The default value of the output GPIO line is the opposite of the
// polarity.
int default_val =
(config.gpio_polarity() == GPIO_POLARITY_ACTIVE_LOW) ? 1 : 0;
if (gpiod_line_request(line, &request_config, default_val) < 0) {
return absl::InternalError("Failed to request GPIO line");
}
} else {
if (gpiod_line_request(line, &request_config, /*default_val=*/0) < 0) {
return absl::InternalError("Failed to request GPIO line");
}
}
int fd = -1;
if (config.request_type_case() == GpioConfig::kGpioEventConfig) {
fd = gpiod_line_event_get_fd(line);
if (fd < 0) {
return absl::InternalError("Failed to get GPIO line event fd");
}
}
return absl::WrapUnique(new Gpio(config, GpioLineUniquePtr(line), fd));
}
} // namespace milotic_tlbmc