blob: be119c6a5ac332e6c13dc3420c5d80594f152d41 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "aml-gpio.h"
#include <lib/ddk/metadata.h>
#include <lib/ddk/platform-defs.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/driver/component/cpp/node_add_args.h>
#include <lib/fpromise/bridge.h>
#include <algorithm>
#include <cstdint>
#include <memory>
#include <bind/fuchsia/hardware/gpioimpl/cpp/bind.h>
#include <fbl/alloc_checker.h>
#include "a1-blocks.h"
#include "a113-blocks.h"
#include "a5-blocks.h"
#include "s905d2-blocks.h"
namespace {
constexpr int kAltFnMax = 15;
constexpr int kMaxPinsInDSReg = 16;
constexpr int kGpioInterruptPolarityShift = 16;
constexpr int kMaxGpioIndex = 255;
constexpr int kBitsPerGpioInterrupt = 8;
constexpr int kBitsPerFilterSelect = 4;
uint32_t GetUnusedIrqIndex(uint8_t status) {
// First isolate the rightmost 0-bit
auto zero_bit_set = static_cast<uint8_t>(~status & (status + 1));
// Count no. of leading zeros
return __builtin_ctz(zero_bit_set);
}
// Supported Drive Strengths
enum DriveStrength {
DRV_500UA,
DRV_2500UA,
DRV_3000UA,
DRV_4000UA,
};
} // namespace
namespace gpio {
// MMIO indices (based on aml-gpio.c gpio_mmios)
enum {
MMIO_GPIO = 0,
MMIO_GPIO_AO = 1,
MMIO_GPIO_INTERRUPTS = 2,
MMIO_COUNT,
};
void AmlGpioDriver::Start(fdf::StartCompleter completer) {
parent_.Bind(std::move(node()), dispatcher());
compat_server_.Begin(
incoming(), outgoing(), node_name(), name(),
[this, completer = std::move(completer)](zx::result<> result) mutable {
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to initialize compat server: %s", result.status_string());
return completer(result.take_error());
}
OnCompatServerInitialized(std::move(completer));
},
compat::ForwardMetadata::Some({DEVICE_METADATA_GPIO_PINS, DEVICE_METADATA_GPIO_CONTROLLER,
DEVICE_METADATA_GPIO_INIT,
DEVICE_METADATA_SCHEDULER_ROLE_NAME}));
}
void AmlGpioDriver::OnCompatServerInitialized(fdf::StartCompleter completer) {
zx::result pdev_client = incoming()->Connect<fuchsia_hardware_platform_device::Service::Device>();
if (pdev_client.is_error()) {
FDF_LOG(ERROR, "Failed to connect to platform device: %s", pdev_client.status_string());
return completer(pdev_client.take_error());
}
pdev_.Bind(*std::move(pdev_client), dispatcher());
pdev_->GetNodeDeviceInfo().Then([this, completer = std::move(completer)](auto& info) mutable {
if (!info.ok()) {
FDF_LOG(ERROR, "Call to get device info failed: %s", info.status_string());
return completer(zx::error(info.status()));
}
if (info->is_error()) {
FDF_LOG(ERROR, "Failed to get device info: %s", zx_status_get_string(info.status()));
return completer(info->take_error());
}
if (!info->value()->has_pid() || !info->value()->has_irq_count()) {
FDF_LOG(ERROR, "No pid or irq_count entry in device info");
return completer(zx::error(ZX_ERR_BAD_STATE));
}
OnGetNodeDeviceInfo(*info->value(), std::move(completer));
});
}
void AmlGpioDriver::OnGetNodeDeviceInfo(
const fuchsia_hardware_platform_device::wire::NodeDeviceInfo& info,
fdf::StartCompleter completer) {
if (info.pid() == 0) {
// TODO(https://fxbug.dev/318736574) : Remove and rely only on GetDeviceInfo.
pdev_->GetBoardInfo().Then([this, irq_count = info.irq_count(),
completer = std::move(completer)](auto& board_info) mutable {
if (!board_info.ok()) {
FDF_LOG(ERROR, "Call to get board info failed: %s", board_info.status_string());
return completer(zx::error(board_info.status()));
}
if (board_info->is_error()) {
FDF_LOG(ERROR, "Failed to get board info: %s", zx_status_get_string(board_info.status()));
return completer(board_info->take_error());
}
if (!board_info->value()->has_vid() || !board_info->value()->has_pid()) {
FDF_LOG(ERROR, "No vid or pid entry in board info");
return completer(zx::error(ZX_ERR_BAD_STATE));
}
OnGetBoardInfo(*board_info->value(), irq_count, std::move(completer));
});
} else {
MapMmios(info.pid(), info.irq_count(), std::move(completer));
}
}
void AmlGpioDriver::OnGetBoardInfo(
const fuchsia_hardware_platform_device::wire::BoardInfo& board_info, uint32_t irq_count,
fdf::StartCompleter completer) {
uint32_t pid = 0;
if (board_info.vid() == PDEV_VID_AMLOGIC) {
pid = board_info.pid();
} else if (board_info.vid() == PDEV_VID_GOOGLE) {
switch (board_info.pid()) {
case PDEV_PID_ASTRO:
pid = PDEV_PID_AMLOGIC_S905D2;
break;
case PDEV_PID_SHERLOCK:
pid = PDEV_PID_AMLOGIC_T931;
break;
case PDEV_PID_NELSON:
pid = PDEV_PID_AMLOGIC_S905D3;
break;
default:
FDF_LOG(ERROR, "Unsupported PID 0x%x for VID 0x%x", board_info.pid(), board_info.vid());
return completer(zx::error(ZX_ERR_INVALID_ARGS));
}
} else if (board_info.vid() == PDEV_VID_KHADAS) {
switch (board_info.pid()) {
case PDEV_PID_VIM3:
pid = PDEV_PID_AMLOGIC_A311D;
break;
default:
FDF_LOG(ERROR, "Unsupported PID 0x%x for VID 0x%x", board_info.pid(), board_info.vid());
return completer(zx::error(ZX_ERR_INVALID_ARGS));
}
} else {
FDF_LOG(ERROR, "Unsupported VID 0x%x", board_info.vid());
return completer(zx::error(ZX_ERR_INVALID_ARGS));
}
MapMmios(pid, irq_count, std::move(completer));
}
void AmlGpioDriver::MapMmios(uint32_t pid, uint32_t irq_count, fdf::StartCompleter completer) {
constexpr int kMmioIds[] = {MMIO_GPIO, MMIO_GPIO_AO, MMIO_GPIO_INTERRUPTS};
static_assert(std::size(kMmioIds) == MMIO_COUNT);
std::vector<fpromise::promise<fdf::MmioBuffer, zx_status_t>> promises;
for (const int mmio_id : kMmioIds) {
promises.push_back(MapMmio(pdev_, mmio_id));
}
auto task =
fpromise::join_promise_vector(std::move(promises))
.then([this, pid, irq_count, completer = std::move(completer)](
fpromise::result<std::vector<fpromise::result<fdf::MmioBuffer, zx_status_t>>>&
results) mutable {
ZX_DEBUG_ASSERT(results.is_ok());
std::vector<fdf::MmioBuffer> mmios;
for (auto& result : results.value()) {
if (result.is_error()) {
return completer(zx::error(result.error()));
}
mmios.push_back(std::move(result.value()));
}
AddNode(pid, irq_count, std::move(mmios), std::move(completer));
});
executor_.schedule_task(std::move(task));
}
void AmlGpioDriver::AddNode(uint32_t pid, uint32_t irq_count, std::vector<fdf::MmioBuffer> mmios,
fdf::StartCompleter completer) {
ZX_DEBUG_ASSERT(mmios.size() == MMIO_COUNT);
cpp20::span<const AmlGpioBlock> gpio_blocks;
const AmlGpioInterrupt* gpio_interrupt;
switch (pid) {
case PDEV_PID_AMLOGIC_A113:
gpio_blocks = {a113_gpio_blocks, std::size(a113_gpio_blocks)};
gpio_interrupt = &a113_interrupt_block;
break;
case PDEV_PID_AMLOGIC_S905D2:
case PDEV_PID_AMLOGIC_T931:
case PDEV_PID_AMLOGIC_A311D:
case PDEV_PID_AMLOGIC_S905D3:
// S905D2, T931, A311D, S905D3 are identical.
gpio_blocks = {s905d2_gpio_blocks, std::size(s905d2_gpio_blocks)};
gpio_interrupt = &s905d2_interrupt_block;
break;
case PDEV_PID_AMLOGIC_A5:
gpio_blocks = {a5_gpio_blocks, std::size(a5_gpio_blocks)};
gpio_interrupt = &a5_interrupt_block;
break;
case PDEV_PID_AMLOGIC_A1:
gpio_blocks = {a1_gpio_blocks, std::size(a1_gpio_blocks)};
gpio_interrupt = &a1_interrupt_block;
break;
default:
FDF_LOG(ERROR, "Unsupported SOC PID %u", pid);
return completer(zx::error(ZX_ERR_INVALID_ARGS));
}
fbl::AllocChecker ac;
fbl::Array<uint16_t> irq_info(new (&ac) uint16_t[irq_count], irq_count);
if (!ac.check()) {
FDF_LOG(ERROR, "irq_info alloc failed");
return completer(zx::error(ZX_ERR_NO_MEMORY));
}
std::fill(irq_info.begin(), irq_info.end(), kMaxGpioIndex + 1); // initialize irq_info
zx::result pdev_client = incoming()->Connect<fuchsia_hardware_platform_device::Service::Device>();
if (pdev_client.is_error()) {
FDF_LOG(ERROR, "Failed to connect to platform device: %s", pdev_client.status_string());
return completer(pdev_client.take_error());
}
device_.reset(new (&ac)
AmlGpio(*std::move(pdev_client), std::move(mmios[MMIO_GPIO]),
std::move(mmios[MMIO_GPIO_AO]), std::move(mmios[MMIO_GPIO_INTERRUPTS]),
gpio_blocks, gpio_interrupt, pid, std::move(irq_info)));
if (!ac.check()) {
FDF_LOG(ERROR, "Device object alloc failed");
return completer(zx::error(ZX_ERR_NO_MEMORY));
}
{
fuchsia_hardware_gpioimpl::Service::InstanceHandler handler({
.device = device_->CreateHandler(),
});
auto result = outgoing()->AddService<fuchsia_hardware_gpioimpl::Service>(std::move(handler));
if (result.is_error()) {
FDF_LOG(ERROR, "AddService failed: %s", result.status_string());
return completer(zx::error(result.error_value()));
}
}
zx::result controller_endpoints =
fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>();
if (!controller_endpoints.is_ok()) {
FDF_LOG(ERROR, "Failed to create controller endpoints: %s",
controller_endpoints.status_string());
return completer(controller_endpoints.take_error());
}
controller_.Bind(std::move(controller_endpoints->client), dispatcher());
fidl::Arena arena;
fidl::VectorView<fuchsia_driver_framework::wire::NodeProperty> properties(arena, 1);
properties[0] = fdf::MakeProperty(arena, bind_fuchsia_hardware_gpioimpl::SERVICE,
bind_fuchsia_hardware_gpioimpl::SERVICE_DRIVERTRANSPORT);
std::vector offers = compat_server_.CreateOffers2(arena);
offers.push_back(
fdf::MakeOffer2<fuchsia_hardware_gpioimpl::Service>(arena, component::kDefaultInstance));
const auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, name())
.offers2(arena, std::move(offers))
.properties(properties)
.Build();
parent_->AddChild(args, std::move(controller_endpoints->server), {})
.Then([completer = std::move(completer)](auto& result) mutable {
if (!result.ok()) {
FDF_LOG(ERROR, "Call to add child failed: %s", result.status_string());
return completer(zx::error(result.status()));
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to add child");
return completer(zx::error(ZX_ERR_INTERNAL));
}
completer(zx::ok());
});
}
zx_status_t AmlGpio::AmlPinToBlock(const uint32_t pin, const AmlGpioBlock** out_block,
uint32_t* out_pin_index) const {
ZX_DEBUG_ASSERT(out_block && out_pin_index);
for (const AmlGpioBlock& gpio_block : gpio_blocks_) {
const uint32_t end_pin = gpio_block.start_pin + gpio_block.pin_count;
if (pin >= gpio_block.start_pin && pin < end_pin) {
*out_block = &gpio_block;
*out_pin_index = pin - gpio_block.pin_block + gpio_block.output_shift;
return ZX_OK;
}
}
return ZX_ERR_NOT_FOUND;
}
void AmlGpio::ConfigIn(fuchsia_hardware_gpioimpl::wire::GpioImplConfigInRequest* request,
fdf::Arena& arena, ConfigInCompleter::Sync& completer) {
zx_status_t status;
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(request->index, &block, &pinindex)) != ZX_OK) {
FDF_LOG(ERROR, "Pin not found %u", request->index);
return completer.buffer(arena).ReplyError(status);
}
const uint32_t pinmask = 1 << pinindex;
uint32_t regval = mmios_[block->mmio_index].Read32(block->oen_offset * sizeof(uint32_t));
// Set the GPIO as pull-up or pull-down
auto pull = request->flags;
uint32_t pull_reg_val = mmios_[block->mmio_index].Read32(block->pull_offset * sizeof(uint32_t));
uint32_t pull_en_reg_val =
mmios_[block->mmio_index].Read32(block->pull_en_offset * sizeof(uint32_t));
if (pull == fuchsia_hardware_gpio::GpioFlags::kNoPull) {
pull_en_reg_val &= ~pinmask;
} else {
if (pull == fuchsia_hardware_gpio::GpioFlags::kPullUp) {
pull_reg_val |= pinmask;
} else {
pull_reg_val &= ~pinmask;
}
pull_en_reg_val |= pinmask;
}
mmios_[block->mmio_index].Write32(pull_reg_val, block->pull_offset * sizeof(uint32_t));
mmios_[block->mmio_index].Write32(pull_en_reg_val, block->pull_en_offset * sizeof(uint32_t));
regval |= pinmask;
mmios_[block->mmio_index].Write32(regval, block->oen_offset * sizeof(uint32_t));
completer.buffer(arena).ReplySuccess();
}
void AmlGpio::ConfigOut(fuchsia_hardware_gpioimpl::wire::GpioImplConfigOutRequest* request,
fdf::Arena& arena, ConfigOutCompleter::Sync& completer) {
zx_status_t status;
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(request->index, &block, &pinindex)) != ZX_OK) {
FDF_LOG(ERROR, "Pin not found %u", request->index);
return completer.buffer(arena).ReplyError(status);
}
const uint32_t pinmask = 1 << pinindex;
// Set value before configuring for output
uint32_t regval = mmios_[block->mmio_index].Read32(block->output_offset * sizeof(uint32_t));
if (request->initial_value) {
regval |= pinmask;
} else {
regval &= ~pinmask;
}
mmios_[block->mmio_index].Write32(regval, block->output_offset * sizeof(uint32_t));
regval = mmios_[block->mmio_index].Read32(block->oen_offset * sizeof(uint32_t));
regval &= ~pinmask;
mmios_[block->mmio_index].Write32(regval, block->oen_offset * sizeof(uint32_t));
completer.buffer(arena).ReplySuccess();
}
// Configure a pin for an alternate function specified by fn
void AmlGpio::SetAltFunction(
fuchsia_hardware_gpioimpl::wire::GpioImplSetAltFunctionRequest* request, fdf::Arena& arena,
SetAltFunctionCompleter::Sync& completer) {
if (request->function > kAltFnMax) {
FDF_LOG(ERROR, "Pin mux alt config out of range %lu", request->function);
return completer.buffer(arena).ReplyError(ZX_ERR_OUT_OF_RANGE);
}
zx_status_t status;
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(request->index, &block, &pinindex)) != ZX_OK) {
FDF_LOG(ERROR, "Pin not found %u", request->index);
return completer.buffer(arena).ReplyError(status);
}
// Sanity Check: pin_to_block must return a block that contains `pin`
// therefore `pin` must be greater than or equal to the first
// pin of the block.
ZX_DEBUG_ASSERT(request->index >= block->start_pin);
// Each Pin Mux is controlled by a 4 bit wide field in `reg`
// Compute the offset for this pin.
uint32_t pin_shift = (request->index - block->start_pin) * 4;
pin_shift += block->output_shift;
const uint32_t mux_mask = ~(0x0F << pin_shift);
const auto fn_val = static_cast<uint32_t>(request->function << pin_shift);
uint32_t regval = mmios_[block->mmio_index].Read32(block->mux_offset * sizeof(uint32_t));
regval &= mux_mask; // Remove the previous value for the mux
regval |= fn_val; // Assign the new value to the mux
mmios_[block->mmio_index].Write32(regval, block->mux_offset * sizeof(uint32_t));
completer.buffer(arena).ReplySuccess();
}
void AmlGpio::Read(fuchsia_hardware_gpioimpl::wire::GpioImplReadRequest* request, fdf::Arena& arena,
ReadCompleter::Sync& completer) {
zx_status_t status;
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(request->index, &block, &pinindex)) != ZX_OK) {
FDF_LOG(ERROR, "Pin not found %u", request->index);
return completer.buffer(arena).ReplyError(status);
}
uint32_t regval = mmios_[block->mmio_index].Read32(block->input_offset * sizeof(uint32_t));
const uint32_t readmask = 1 << pinindex;
completer.buffer(arena).ReplySuccess(regval & readmask ? 1 : 0);
}
void AmlGpio::Write(fuchsia_hardware_gpioimpl::wire::GpioImplWriteRequest* request,
fdf::Arena& arena, WriteCompleter::Sync& completer) {
zx_status_t status;
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(request->index, &block, &pinindex)) != ZX_OK) {
FDF_LOG(ERROR, "Pin not found %u", request->index);
return completer.buffer(arena).ReplyError(status);
}
uint32_t regval = mmios_[block->mmio_index].Read32(block->output_offset * sizeof(uint32_t));
if (request->value) {
regval |= 1 << pinindex;
} else {
regval &= ~(1 << pinindex);
}
mmios_[block->mmio_index].Write32(regval, block->output_offset * sizeof(uint32_t));
completer.buffer(arena).ReplySuccess();
}
void AmlGpio::GetInterrupt(fuchsia_hardware_gpioimpl::wire::GpioImplGetInterruptRequest* request,
fdf::Arena& arena, GetInterruptCompleter::Sync& completer) {
zx_status_t status = ZX_OK;
if (request->index > kMaxGpioIndex) {
return completer.buffer(arena).ReplyError(ZX_ERR_INVALID_ARGS);
}
uint32_t index = GetUnusedIrqIndex(irq_status_);
if (index > irq_info_.size()) {
FDF_LOG(ERROR, "No free IRQ indicies %u, irq_count = %zu", (int)index, irq_info_.size());
return completer.buffer(arena).ReplyError(ZX_ERR_NO_RESOURCES);
}
for (uint16_t irq : irq_info_) {
if (irq == request->index) {
FDF_LOG(ERROR, "GPIO Interrupt already configured for this pin %u", (int)index);
return completer.buffer(arena).ReplyError(ZX_ERR_ALREADY_EXISTS);
}
}
FDF_LOG(DEBUG, "GPIO Interrupt index %d allocated", (int)index);
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(request->index, &block, &pinindex)) != ZX_OK) {
FDF_LOG(ERROR, "Pin not found %u", request->index);
return completer.buffer(arena).ReplyError(status);
}
// Configure GPIO Interrupt EDGE and Polarity
uint32_t mode_reg_val =
mmio_interrupt_.Read32(gpio_interrupt_->edge_polarity_offset * sizeof(uint32_t));
switch (request->flags & ZX_INTERRUPT_MODE_MASK) {
case ZX_INTERRUPT_MODE_EDGE_LOW:
mode_reg_val |= (1 << index);
mode_reg_val |= ((1 << index) << kGpioInterruptPolarityShift);
break;
case ZX_INTERRUPT_MODE_EDGE_HIGH:
mode_reg_val |= (1 << index);
mode_reg_val &= ~((1 << index) << kGpioInterruptPolarityShift);
break;
case ZX_INTERRUPT_MODE_LEVEL_LOW:
mode_reg_val &= ~(1 << index);
mode_reg_val |= ((1 << index) << kGpioInterruptPolarityShift);
break;
case ZX_INTERRUPT_MODE_LEVEL_HIGH:
mode_reg_val &= ~(1 << index);
mode_reg_val &= ~((1 << index) << kGpioInterruptPolarityShift);
break;
default:
return completer.buffer(arena).ReplyError(ZX_ERR_INVALID_ARGS);
}
mmio_interrupt_.Write32(mode_reg_val, gpio_interrupt_->edge_polarity_offset * sizeof(uint32_t));
// Configure Interrupt Select Filter
mmio_interrupt_.SetBits32(0x7 << (index * kBitsPerFilterSelect),
gpio_interrupt_->filter_select_offset * sizeof(uint32_t));
// Configure GPIO interrupt
const uint32_t pin_select_bit = index * kBitsPerGpioInterrupt;
const uint32_t pin_select_offset = gpio_interrupt_->pin_select_offset + (pin_select_bit / 32);
const uint32_t pin_select_index = pin_select_bit % 32;
// Select GPIO IRQ(index) and program it to the requested GPIO PIN
mmio_interrupt_.ModifyBits32((request->index - block->pin_block) + block->pin_start,
pin_select_index, kBitsPerGpioInterrupt,
pin_select_offset * sizeof(uint32_t));
// Hold this IRQ index while the GetInterrupt call is pending.
irq_status_ |= static_cast<uint8_t>(1 << index);
irq_info_[index] = static_cast<uint16_t>(request->index);
// Create Interrupt Object, removing the requested polarity, since the polarity is managed
// by the GPIO HW via MMIO setting above.
// Interrupt modes are represented by values (not a bitmask) within the ZX_INTERRUPT_MODE_MASK.
// We only change ZX_INTERRUPT_MODE_[EDGE|LEVEL]_LOW values, passing other values in flags
// unchanged including values outside ZX_INTERRUPT_MODE_MASK.
uint32_t mode = request->flags & ZX_INTERRUPT_MODE_MASK;
uint32_t flags = request->flags & ~ZX_INTERRUPT_MODE_MASK;
if (mode == ZX_INTERRUPT_MODE_EDGE_LOW) {
mode = ZX_INTERRUPT_MODE_EDGE_HIGH;
} else if (mode == ZX_INTERRUPT_MODE_LEVEL_LOW) {
mode = ZX_INTERRUPT_MODE_LEVEL_HIGH;
}
flags |= mode;
pdev_->GetInterruptById(index, flags)
.Then([this, index, irq_index = request->index,
completer = completer.ToAsync()](auto& out_irq) mutable {
fdf::Arena arena('GPIO');
// ReleaseInterrupt was called before we got the interrupt from platform bus.
if (irq_info_[index] != irq_index) {
FDF_LOG(WARNING, "Pin %u interrupt released before it could be returned to the client",
irq_index);
return completer.buffer(arena).ReplyError(ZX_ERR_CANCELED);
}
// The call failed, release this IRQ index.
if (!out_irq.ok() || out_irq->is_error()) {
irq_status_ &= static_cast<uint8_t>(~(1 << index));
irq_info_[index] = kMaxGpioIndex + 1;
}
if (!out_irq.ok()) {
FDF_LOG(ERROR, "Call to pdev_get_interrupt failed: %s", out_irq.status_string());
return completer.buffer(arena).ReplyError(out_irq.status());
}
if (out_irq->is_error()) {
FDF_LOG(ERROR, "pdev_get_interrupt failed: %s", out_irq.status_string());
return completer.buffer(arena).Reply(out_irq->take_error());
}
completer.buffer(arena).ReplySuccess(std::move(out_irq->value()->irq));
});
}
void AmlGpio::ReleaseInterrupt(
fuchsia_hardware_gpioimpl::wire::GpioImplReleaseInterruptRequest* request, fdf::Arena& arena,
ReleaseInterruptCompleter::Sync& completer) {
for (uint32_t i = 0; i < irq_info_.size(); i++) {
if (irq_info_[i] == request->index) {
irq_status_ &= static_cast<uint8_t>(~(1 << i));
irq_info_[i] = kMaxGpioIndex + 1;
return completer.buffer(arena).ReplySuccess();
}
}
return completer.buffer(arena).ReplyError(ZX_ERR_NOT_FOUND);
}
void AmlGpio::SetPolarity(fuchsia_hardware_gpioimpl::wire::GpioImplSetPolarityRequest* request,
fdf::Arena& arena, SetPolarityCompleter::Sync& completer) {
int irq_index = -1;
if (request->index > kMaxGpioIndex) {
return completer.buffer(arena).ReplyError(ZX_ERR_INVALID_ARGS);
}
for (uint32_t i = 0; i < irq_info_.size(); i++) {
if (irq_info_[i] == request->index) {
irq_index = i;
break;
}
}
if (irq_index == -1) {
return completer.buffer(arena).ReplyError(ZX_ERR_NOT_FOUND);
}
// Configure GPIO Interrupt EDGE and Polarity
if (request->polarity == fuchsia_hardware_gpio::GpioPolarity::kHigh) {
mmio_interrupt_.ClearBits32(((1 << irq_index) << kGpioInterruptPolarityShift),
gpio_interrupt_->edge_polarity_offset * sizeof(uint32_t));
} else {
mmio_interrupt_.SetBits32(((1 << irq_index) << kGpioInterruptPolarityShift),
gpio_interrupt_->edge_polarity_offset * sizeof(uint32_t));
}
completer.buffer(arena).ReplySuccess();
}
void AmlGpio::GetDriveStrength(
fuchsia_hardware_gpioimpl::wire::GpioImplGetDriveStrengthRequest* request, fdf::Arena& arena,
GetDriveStrengthCompleter::Sync& completer) {
zx_status_t st;
if (pid_ == PDEV_PID_AMLOGIC_A113) {
return completer.buffer(arena).ReplyError(ZX_ERR_NOT_SUPPORTED);
}
const AmlGpioBlock* block;
uint32_t pinindex;
if ((st = AmlPinToBlock(request->index, &block, &pinindex)) != ZX_OK) {
FDF_LOG(ERROR, "Pin not found %u", request->index);
return completer.buffer(arena).ReplyError(st);
}
pinindex = request->index - block->pin_block;
if (pinindex >= kMaxPinsInDSReg) {
pinindex = pinindex % kMaxPinsInDSReg;
}
const uint32_t shift = pinindex * 2;
uint32_t regval = mmios_[block->mmio_index].Read32(block->ds_offset * sizeof(uint32_t));
uint32_t value = (regval >> shift) & 0x3;
uint64_t out{};
switch (value) {
case DRV_500UA:
out = 500;
break;
case DRV_2500UA:
out = 2500;
break;
case DRV_3000UA:
out = 3000;
break;
case DRV_4000UA:
out = 4000;
break;
default:
FDF_LOG(ERROR, "Unexpected drive strength value: %u", value);
return completer.buffer(arena).ReplyError(ZX_ERR_INTERNAL);
}
completer.buffer(arena).ReplySuccess(out);
}
void AmlGpio::SetDriveStrength(
fuchsia_hardware_gpioimpl::wire::GpioImplSetDriveStrengthRequest* request, fdf::Arena& arena,
SetDriveStrengthCompleter::Sync& completer) {
zx_status_t status;
if (pid_ == PDEV_PID_AMLOGIC_A113) {
return completer.buffer(arena).ReplyError(ZX_ERR_NOT_SUPPORTED);
}
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(request->index, &block, &pinindex)) != ZX_OK) {
FDF_LOG(ERROR, "Pin not found %u", request->index);
return completer.buffer(arena).ReplyError(status);
}
DriveStrength ds_val = DRV_4000UA;
uint64_t ua = request->ds_ua;
if (ua <= 500) {
ds_val = DRV_500UA;
ua = 500;
} else if (ua <= 2500) {
ds_val = DRV_2500UA;
ua = 2500;
} else if (ua <= 3000) {
ds_val = DRV_3000UA;
ua = 3000;
} else if (ua <= 4000) {
ds_val = DRV_4000UA;
ua = 4000;
} else {
FDF_LOG(ERROR, "Invalid drive strength %lu, default to 4000 uA", ua);
ds_val = DRV_4000UA;
ua = 4000;
}
pinindex = request->index - block->pin_block;
if (pinindex >= kMaxPinsInDSReg) {
pinindex = pinindex % kMaxPinsInDSReg;
}
// 2 bits for each pin
const uint32_t shift = pinindex * 2;
const uint32_t mask = ~(0x3 << shift);
uint32_t regval = mmios_[block->mmio_index].Read32(block->ds_offset * sizeof(uint32_t));
regval = (regval & mask) | (ds_val << shift);
mmios_[block->mmio_index].Write32(regval, block->ds_offset * sizeof(uint32_t));
completer.buffer(arena).ReplySuccess(ua);
}
void AmlGpio::GetPins(fdf::Arena& arena, GetPinsCompleter::Sync& completer) {}
void AmlGpio::GetInitSteps(fdf::Arena& arena, GetInitStepsCompleter::Sync& completer) {}
void AmlGpio::GetControllerId(fdf::Arena& arena, GetControllerIdCompleter::Sync& completer) {
completer.buffer(arena).Reply(0);
}
fpromise::promise<fdf::MmioBuffer, zx_status_t> AmlGpioDriver::MapMmio(
fidl::WireClient<fuchsia_hardware_platform_device::Device>& pdev, uint32_t mmio_id) {
fpromise::bridge<fdf::MmioBuffer, zx_status_t> bridge;
pdev->GetMmioById(mmio_id).Then(
[mmio_id, completer = std::move(bridge.completer)](auto& result) mutable {
if (!result.ok()) {
FDF_LOG(ERROR, "Call to get MMIO %u failed: %s", mmio_id, result.status_string());
return completer.complete_error(result.status());
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to get MMIO %u: %s", mmio_id,
zx_status_get_string(result->error_value()));
return completer.complete_error(result->error_value());
}
auto& mmio = *result->value();
if (!mmio.has_offset() || !mmio.has_size() || !mmio.has_vmo()) {
FDF_LOG(ERROR, "Invalid MMIO returned for ID %u", mmio_id);
return completer.complete_error(ZX_ERR_BAD_STATE);
}
zx::result mmio_buffer = fdf::MmioBuffer::Create(
mmio.offset(), mmio.size(), std::move(mmio.vmo()), ZX_CACHE_POLICY_UNCACHED_DEVICE);
if (mmio_buffer.is_error()) {
FDF_LOG(ERROR, "Failed to map MMIO %u: %s", mmio_id,
zx_status_get_string(mmio_buffer.error_value()));
return completer.complete_error(mmio_buffer.error_value());
}
completer.complete_ok(*std::move(mmio_buffer));
});
return bridge.consumer.promise_or(fpromise::error(ZX_ERR_BAD_STATE));
}
} // namespace gpio
FUCHSIA_DRIVER_EXPORT(gpio::AmlGpioDriver);