blob: c45f8278364af9d05017022b48d6bdf02641afef [file] [log] [blame]
// Copyright 2019 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 "qcom-gpio.h"
#include <algorithm>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <fbl/alloc_checker.h>
#include <fbl/array.h>
namespace {
uint64_t kPortKeyIrqMsg = 0x00;
uint64_t kPortKeyTerminate = 0x01;
} // namespace
namespace gpio {
int QcomGpioDevice::Thread() {
while (1) {
zx_port_packet_t packet;
auto status = port_.wait(zx::time::infinite(), &packet);
if (status != ZX_OK) {
zxlogf(ERROR, "%s port wait failed: %d", __func__, status);
return thrd_error;
}
zxlogf(TRACE, "%s msg on port key %lu", __func__, packet.key);
if (packet.key == kPortKeyTerminate) {
zxlogf(INFO, "QCOM GPIO thread terminating");
return 0;
}
size_t index = 0;
status = enabled_ints_cache_.Find(true, 0, kGpioMax, 1, &index);
if (status != ZX_OK) {
zxlogf(ERROR, "%s no interrupt found in cache %d", __func__, status);
}
while (status == ZX_OK) {
zxlogf(TRACE, "%s msg on port INT %lu", __func__, index);
if (status_int_.Status(index)) {
status = interrupts_[index].trigger(0, zx::time(packet.interrupt.timestamp));
if (status != ZX_OK) {
zxlogf(ERROR, "%s zx_interrupt_trigger failed %d", __func__, status);
}
status_int_.Clear(index);
} else {
zxlogf(ERROR, "%s interrupt %lu not enabled in reg", __func__, index);
}
status = enabled_ints_cache_.Find(true, index + 1, kGpioMax, 1, &index);
if (status != ZX_ERR_NO_RESOURCES) { // not just not-found in cache.
zxlogf(ERROR, "%s error in reading from cache %d", __func__, status);
}
}
combined_int_.ack();
}
}
zx_status_t QcomGpioDevice::GpioImplConfigIn(uint32_t index, uint32_t flags) {
if (index >= kGpioMax) {
return ZX_ERR_INVALID_ARGS;
}
GpioCfgReg::SetMode(&gpio_mmio_, index, GpioCfgReg::kModeGpio);
GpioCfgReg::SetOut(&gpio_mmio_, index, false);
const uint32_t pull_mode = flags & GPIO_PULL_MASK;
// clang-format off
switch (pull_mode) {
case GPIO_NO_PULL: GpioCfgReg::SetPullNone(&gpio_mmio_, index); break;
case GPIO_PULL_DOWN: GpioCfgReg::SetPullDown(&gpio_mmio_, index); break;
case GPIO_PULL_UP: GpioCfgReg::SetPullUp (&gpio_mmio_, index); break;
default: return ZX_ERR_NOT_SUPPORTED;
}
// clang-format on
return ZX_OK;
}
zx_status_t QcomGpioDevice::GpioImplConfigOut(uint32_t index, uint8_t initial_value) {
if (index >= kGpioMax) {
return ZX_ERR_INVALID_ARGS;
}
GpioCfgReg::SetMode(&gpio_mmio_, index, GpioCfgReg::kModeGpio);
GpioCfgReg::SetOut(&gpio_mmio_, index, true);
return GpioImplWrite(index, initial_value);
}
zx_status_t QcomGpioDevice::GpioImplSetAltFunction(uint32_t index, uint64_t function) {
if (index >= kGpioMax) {
return ZX_ERR_INVALID_ARGS;
}
if (function >= GpioCfgReg::kModeMax) {
return ZX_ERR_OUT_OF_RANGE;
}
GpioCfgReg::SetMode(&gpio_mmio_, index, static_cast<uint32_t>(function));
return ZX_OK;
}
zx_status_t QcomGpioDevice::GpioImplSetDriveStrength(uint32_t index, uint8_t mA) {
if (index >= kGpioMax) {
return ZX_ERR_INVALID_ARGS;
}
uint8_t supported_mAs[] = {2, 4, 6, 8, 10, 12, 14, 16};
if (std::find(std::begin(supported_mAs), std::end(supported_mAs), mA) ==
std::end(supported_mAs)) {
return ZX_ERR_NOT_SUPPORTED;
}
GpioCfgReg::SetStrength(&gpio_mmio_, index, mA);
return ZX_OK;
}
zx_status_t QcomGpioDevice::GpioImplRead(uint32_t index, uint8_t* out_value) {
if (index >= kGpioMax) {
return ZX_ERR_INVALID_ARGS;
}
*out_value = static_cast<uint8_t>(in_out_.GetVal(index));
return ZX_OK;
}
zx_status_t QcomGpioDevice::GpioImplWrite(uint32_t index, uint8_t value) {
if (index >= kGpioMax) {
return ZX_ERR_INVALID_ARGS;
}
in_out_.SetVal(index, value);
return ZX_OK;
}
zx_status_t QcomGpioDevice::GpioImplGetInterrupt(uint32_t index, uint32_t flags,
zx::interrupt* out_irq) {
if (index >= kGpioMax) {
return ZX_ERR_INVALID_ARGS;
}
zx::interrupt irq;
auto status = zx::interrupt::create(zx::resource(), index, ZX_INTERRUPT_VIRTUAL, &irq);
if (status != ZX_OK) {
zxlogf(ERROR, "%s zx::interrupt::create failed %d", __func__, status);
return status;
}
status = irq.duplicate(ZX_RIGHT_SAME_RIGHTS, out_irq);
if (status != ZX_OK) {
zxlogf(ERROR, "%s interrupt.duplicate failed %d", __func__, status);
return status;
}
// clang-format off
switch (flags & ZX_INTERRUPT_MODE_MASK) {
case ZX_INTERRUPT_MODE_EDGE_LOW: int_cfg_.SetMode(index, Mode::EdgeLow); break;
case ZX_INTERRUPT_MODE_EDGE_HIGH: int_cfg_.SetMode(index, Mode::EdgeHigh); break;
case ZX_INTERRUPT_MODE_LEVEL_LOW: int_cfg_.SetMode(index, Mode::LevelLow); break;
case ZX_INTERRUPT_MODE_LEVEL_HIGH: int_cfg_.SetMode(index, Mode::LevelHigh); break;
case ZX_INTERRUPT_MODE_EDGE_BOTH: int_cfg_.SetMode(index, Mode::EdgeDual); break;
default: return ZX_ERR_INVALID_ARGS;
}
// clang-format on
interrupts_[index] = std::move(irq);
// TODO(andresoportus): Once we define which cases would use them, enable direct interrupts
// (via TlmmDirConnIntReg).
status_int_.Clear(index);
int_cfg_.EnableCombined(index, true);
enabled_ints_cache_.SetOne(index);
zxlogf(TRACE, "%s INT %u enabled", __func__, index);
return ZX_OK;
}
zx_status_t QcomGpioDevice::GpioImplReleaseInterrupt(uint32_t index) {
if (index >= kGpioMax) {
return ZX_ERR_INVALID_ARGS;
}
interrupts_[index].destroy();
interrupts_[index].reset();
int_cfg_.EnableCombined(index, false);
enabled_ints_cache_.ClearOne(index);
zxlogf(TRACE, "%s INT %u disabled", __func__, index);
return ZX_OK;
}
zx_status_t QcomGpioDevice::GpioImplSetPolarity(uint32_t index, uint32_t polarity) {
if (index >= kGpioMax) {
return ZX_ERR_INVALID_ARGS;
}
int_cfg_.SetPolarity(index, static_cast<bool>(polarity));
return ZX_OK;
}
void QcomGpioDevice::ShutDown() {
combined_int_.destroy();
zx_port_packet packet = {kPortKeyTerminate, ZX_PKT_TYPE_USER, ZX_OK, {}};
auto status = port_.queue(&packet);
ZX_ASSERT(status == ZX_OK);
thrd_join(thread_, NULL);
}
void QcomGpioDevice::DdkUnbindNew(ddk::UnbindTxn txn) {
ShutDown();
txn.Reply();
}
void QcomGpioDevice::DdkRelease() { delete this; }
zx_status_t QcomGpioDevice::Bind() {
auto status = pdev_.GetInterrupt(0, &combined_int_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s GetInterrupt failed %d", __func__, status);
return status;
}
status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s port create failed %d", __func__, status);
return status;
}
status = combined_int_.bind(port_, kPortKeyIrqMsg, 0 /*options*/);
if (status != ZX_OK) {
zxlogf(ERROR, "%s interrupt bind failed %d", __func__, status);
return status;
}
fbl::AllocChecker ac;
interrupts_ = fbl::Array(new (&ac) zx::interrupt[kGpioMax], kGpioMax);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
auto cb = [](void* arg) -> int { return reinterpret_cast<QcomGpioDevice*>(arg)->Thread(); };
int rc = thrd_create_with_name(&thread_, cb, this, "qcom-gpio-thread");
if (rc != thrd_success) {
return ZX_ERR_INTERNAL;
}
status = DdkAdd("qcom-gpio");
if (status != ZX_OK) {
zxlogf(ERROR, "%s DdkAdd failed %d", __func__, status);
ShutDown();
return status;
}
return ZX_OK;
}
zx_status_t QcomGpioDevice::Init() {
pbus_protocol_t pbus;
auto status = device_get_protocol(parent(), ZX_PROTOCOL_PBUS, &pbus);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: ZX_PROTOCOL_PBUS not available %d", __func__, status);
return status;
}
gpio_impl_protocol_t gpio_proto = {
.ops = &gpio_impl_protocol_ops_,
.ctx = this,
};
status = pbus_register_protocol(&pbus, ZX_PROTOCOL_GPIO_IMPL, &gpio_proto, sizeof(gpio_proto));
if (status != ZX_OK) {
zxlogf(ERROR, "%s pbus_register_protocol failed %d", __func__, status);
ShutDown();
return status;
}
return enabled_ints_cache_.Reset(kGpioMax); // Clear and resize.
}
zx_status_t QcomGpioDevice::Create(zx_device_t* parent) {
pdev_protocol_t pdev;
zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_PDEV, &pdev);
if (status != ZX_OK) {
zxlogf(ERROR, "%s ZX_PROTOCOL_PDEV not available %d", __func__, status);
return status;
}
mmio_buffer_t gpio_mmio;
status = pdev_map_mmio_buffer(&pdev, 0, ZX_CACHE_POLICY_UNCACHED_DEVICE, &gpio_mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "%s gpio pdev_map_mmio_buffer failed %d", __func__, status);
return status;
}
fbl::AllocChecker ac;
auto dev =
fbl::make_unique_checked<gpio::QcomGpioDevice>(&ac, parent, ddk::MmioBuffer(gpio_mmio));
if (!ac.check()) {
zxlogf(ERROR, "qcom_gpio_bind: ZX_ERR_NO_MEMORY");
return ZX_ERR_NO_MEMORY;
}
status = dev->Bind();
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the memory for dev
auto ptr = dev.release();
return ptr->Init();
}
zx_status_t qcom_gpio_bind(void* ctx, zx_device_t* parent) {
return gpio::QcomGpioDevice::Create(parent);
}
} // namespace gpio