blob: 8c1f1e7b813b857304005dbc2a9d77a656daef58 [file] [log] [blame]
// Copyright 2024 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_digital_io_linux/digital_io.h"
#include <fcntl.h>
#include <linux/gpio.h>
#include "pw_digital_io/digital_io.h"
#include "pw_result/result.h"
#include "pw_status/status.h"
// NOTE: Currently only the v1 userspace ABI is supported.
// TODO: https://pwbug.dev/328268007 - Add support for v2.
namespace pw::digital_io {
namespace {
using internal::OwnedFd;
Result<State> FdGetState(OwnedFd& fd) {
struct gpiohandle_data req = {};
if (fd.ioctl(GPIOHANDLE_GET_LINE_VALUES_IOCTL, &req) < 0) {
return Status::Internal();
}
return req.values[0] ? State::kActive : State::kInactive;
}
} // namespace
// TODO(jrreinhart): Support other flags, e.g.:
// GPIOHANDLE_REQUEST_OPEN_DRAIN,
// GPIOHANDLE_REQUEST_OPEN_SOURCE,
// GPIOHANDLE_REQUEST_BIAS_PULL_UP,
// GPIOHANDLE_REQUEST_BIAS_PULL_DOWN,
// GPIOHANDLE_REQUEST_BIAS_DISABLE,
uint32_t LinuxConfig::GetFlags() const {
uint32_t flags = 0;
switch (polarity) {
case Polarity::kActiveHigh:
break;
case Polarity::kActiveLow:
flags |= GPIOHANDLE_REQUEST_ACTIVE_LOW;
break;
}
return flags;
}
uint32_t LinuxInputConfig::GetFlags() const {
uint32_t flags = LinuxConfig::GetFlags();
flags |= GPIOHANDLE_REQUEST_INPUT;
return flags;
}
uint32_t LinuxOutputConfig::GetFlags() const {
uint32_t flags = LinuxConfig::GetFlags();
flags |= GPIOHANDLE_REQUEST_OUTPUT;
return flags;
}
//
// LinuxDigitalIoChip
//
Result<LinuxDigitalIoChip> LinuxDigitalIoChip::Open(const char* path) {
int fd = open(path, O_RDWR);
if (fd < 0) {
// TODO(jrreinhart): Map errno to Status
return Status::Internal();
}
return LinuxDigitalIoChip(fd);
}
Result<OwnedFd> LinuxDigitalIoChip::Impl::GetLineHandle(uint32_t offset,
uint32_t flags,
uint8_t default_value) {
struct gpiohandle_request req = {
.lineoffsets = {offset},
.flags = flags,
.default_values = {default_value},
.consumer_label = "pw_digital_io_linux",
.lines = 1,
.fd = -1,
};
if (fd_.ioctl(GPIO_GET_LINEHANDLE_IOCTL, &req) < 0) {
return Status::Internal();
}
if (req.fd < 0) {
return Status::Internal();
}
return OwnedFd(req.fd);
}
Result<LinuxDigitalIn> LinuxDigitalIoChip::GetInputLine(
const LinuxInputConfig& config) {
if (!impl_) {
return Status::FailedPrecondition();
}
return LinuxDigitalIn(impl_, config);
}
Result<LinuxDigitalOut> LinuxDigitalIoChip::GetOutputLine(
const LinuxOutputConfig& config) {
if (!impl_) {
return Status::FailedPrecondition();
}
return LinuxDigitalOut(impl_, config);
}
//
// LinuxDigitalIn
//
Status LinuxDigitalIn::DoEnable(bool enable) {
if (enable) {
if (enabled()) {
return OkStatus();
}
PW_TRY_ASSIGN(fd_, chip_->GetLineHandle(config_.index, config_.GetFlags()));
} else {
// Close the open file handle and release the line request.
fd_.Close();
}
return OkStatus();
}
Result<State> LinuxDigitalIn::DoGetState() { return FdGetState(fd_); }
//
// LinuxDigitalOut
//
Status LinuxDigitalOut::DoEnable(bool enable) {
if (enable) {
if (enabled()) {
return OkStatus();
}
uint8_t default_value = config_.default_state == State::kActive;
PW_TRY_ASSIGN(
fd_,
chip_->GetLineHandle(config_.index, config_.GetFlags(), default_value));
} else {
// Close the open file handle and release the line request.
fd_.Close();
}
return OkStatus();
}
Result<State> LinuxDigitalOut::DoGetState() { return FdGetState(fd_); }
Status LinuxDigitalOut::DoSetState(State level) {
struct gpiohandle_data req = {
.values = {(level == State::kActive) ? uint8_t(1) : uint8_t(0)},
};
if (fd_.ioctl(GPIOHANDLE_SET_LINE_VALUES_IOCTL, &req) < 0) {
return Status::Internal();
}
return OkStatus();
}
} // namespace pw::digital_io