blob: 925120e3ec205c61b72031b3d76319dfd55e2bca [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-axg-gpio.h"
#include <lib/ddk/debug.h>
#include <lib/ddk/platform-defs.h>
#include <lib/device-protocol/platform-device.h>
#include <cstdint>
#include <memory>
#include <fbl/alloc_checker.h>
#include "a113-blocks.h"
#include "s905d2-blocks.h"
#include "src/devices/gpio/drivers/aml-axg-gpio/aml-axg-gpio-bind.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_A0 = 1,
MMIO_GPIO_INTERRUPTS = 2,
};
zx_status_t AmlAxgGpio::Create(void* ctx, zx_device_t* parent) {
zx_status_t status;
pbus_protocol_t pbus;
if ((status = device_get_protocol(parent, ZX_PROTOCOL_PBUS, &pbus)) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::Create: ZX_PROTOCOL_PBUS not available");
return status;
}
ddk::PDev pdev(parent);
std::optional<ddk::MmioBuffer> mmio_gpio, mmio_gpio_a0, mmio_interrupt;
if ((status = pdev.MapMmio(MMIO_GPIO, &mmio_gpio)) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::Create: MapMmio failed");
return status;
}
if ((status = pdev.MapMmio(MMIO_GPIO_A0, &mmio_gpio_a0)) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::Create: MapMmio failed");
return status;
}
if ((status = pdev.MapMmio(MMIO_GPIO_INTERRUPTS, &mmio_interrupt)) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::Create: MapMmio failed");
return status;
}
pdev_device_info_t info;
if ((status = pdev.GetDeviceInfo(&info)) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::Create: GetDeviceInfo failed");
return status;
}
const AmlGpioBlock* gpio_blocks;
const AmlGpioInterrupt* gpio_interrupt;
size_t block_count;
switch (info.pid) {
case PDEV_PID_AMLOGIC_A113:
gpio_blocks = a113_gpio_blocks;
block_count = countof(a113_gpio_blocks);
gpio_interrupt = &a113_interrupt_block;
break;
case PDEV_PID_AMLOGIC_S905D2:
case PDEV_PID_AMLOGIC_T931:
case PDEV_PID_AMLOGIC_A311D:
// S905D2, T931, A311D are identical.
gpio_blocks = s905d2_gpio_blocks;
block_count = countof(s905d2_gpio_blocks);
gpio_interrupt = &s905d2_interrupt_block;
break;
default:
zxlogf(ERROR, "AmlAxgGpio::Create: unsupported SOC PID %u", info.pid);
return ZX_ERR_INVALID_ARGS;
}
fbl::AllocChecker ac;
fbl::Array<uint16_t> irq_info(new (&ac) uint16_t[info.irq_count], info.irq_count);
if (!ac.check()) {
zxlogf(ERROR, "AmlAxgGpio::Create: irq_info alloc failed");
return ZX_ERR_NO_MEMORY;
}
for (uint32_t i = 0; i < info.irq_count; i++) {
irq_info[i] = kMaxGpioIndex + 1;
} // initialize irq_info
std::unique_ptr<AmlAxgGpio> device(new (&ac) AmlAxgGpio(
parent, *std::move(mmio_gpio), *std::move(mmio_gpio_a0), *std::move(mmio_interrupt),
gpio_blocks, gpio_interrupt, block_count, std::move(info), std::move(irq_info)));
if (!ac.check()) {
zxlogf(ERROR, "AmlAxgGpio::Create: device object alloc failed");
return ZX_ERR_NO_MEMORY;
}
device->Bind(pbus);
if ((status = device->DdkAdd(
ddk::DeviceAddArgs("aml-axg-gpio").set_proto_id(ZX_PROTOCOL_GPIO_IMPL))) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::Create: DdkAdd failed");
return status;
}
__UNUSED auto* unused = device.release();
return ZX_OK;
}
void AmlAxgGpio::Bind(const pbus_protocol_t& pbus) {
gpio_impl_protocol_t gpio_proto = {
.ops = &gpio_impl_protocol_ops_,
.ctx = this,
};
pbus_register_protocol(&pbus, ZX_PROTOCOL_GPIO_IMPL, reinterpret_cast<uint8_t*>(&gpio_proto),
sizeof(gpio_proto));
}
zx_status_t AmlAxgGpio::AmlPinToBlock(const uint32_t pin, const AmlGpioBlock** out_block,
uint32_t* out_pin_index) const {
ZX_DEBUG_ASSERT(out_block && out_pin_index);
for (size_t i = 0; i < block_count_; i++) {
const AmlGpioBlock& gpio_block = gpio_blocks_[i];
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;
}
zx_status_t AmlAxgGpio::GpioImplConfigIn(uint32_t index, uint32_t flags) {
zx_status_t status;
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(index, &block, &pinindex)) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::GpioImplConfigIn: pin not found %u", index);
return status;
}
const uint32_t pinmask = 1 << pinindex;
{
fbl::AutoLock al(&mmio_lock_);
uint32_t regval = mmios_[block->mmio_index].Read32(block->oen_offset * sizeof(uint32_t));
// Set the GPIO as pull-up or pull-down
uint32_t pull = flags & GPIO_PULL_MASK;
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 & GPIO_NO_PULL) {
pull_en_reg_val &= ~pinmask;
} else {
if (pull & GPIO_PULL_UP) {
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));
}
return ZX_OK;
}
zx_status_t AmlAxgGpio::GpioImplConfigOut(uint32_t index, uint8_t initial_value) {
zx_status_t status;
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(index, &block, &pinindex)) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::GpioImplConfigOut: pin not found %u", index);
return status;
}
const uint32_t pinmask = 1 << pinindex;
{
fbl::AutoLock al(&mmio_lock_);
// Set value before configuring for output
uint32_t regval = mmios_[block->mmio_index].Read32(block->output_offset * sizeof(uint32_t));
if (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));
}
return ZX_OK;
}
// Configure a pin for an alternate function specified by fn
zx_status_t AmlAxgGpio::GpioImplSetAltFunction(const uint32_t pin, const uint64_t fn) {
if (fn > kAltFnMax) {
zxlogf(ERROR, "AmlAxgGpio::GpioImplSetAltFunction: pin mux alt config out of range %lu", fn);
return ZX_ERR_OUT_OF_RANGE;
}
zx_status_t status;
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(pin, &block, &pinindex)) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::GpioImplSetAltFunction: pin not found %u", pin);
return 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(pin >= 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 = (pin - 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>(fn << pin_shift);
{
fbl::AutoLock al(&mmio_lock_);
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));
}
return ZX_OK;
}
zx_status_t AmlAxgGpio::GpioImplRead(uint32_t index, uint8_t* out_value) {
zx_status_t status;
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(index, &block, &pinindex)) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::GpioImplRead: pin not found %u", index);
return status;
}
uint32_t regval = 0;
{
fbl::AutoLock al(&mmio_lock_);
regval = mmios_[block->mmio_index].Read32(block->input_offset * sizeof(uint32_t));
}
const uint32_t readmask = 1 << pinindex;
if (regval & readmask) {
*out_value = 1;
} else {
*out_value = 0;
}
return ZX_OK;
}
zx_status_t AmlAxgGpio::GpioImplWrite(uint32_t index, uint8_t value) {
zx_status_t status;
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(index, &block, &pinindex)) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::GpioImplWrite: pin not found %u", index);
return status;
}
{
fbl::AutoLock al(&mmio_lock_);
uint32_t regval = mmios_[block->mmio_index].Read32(block->output_offset * sizeof(uint32_t));
if (value) {
regval |= 1 << pinindex;
} else {
regval &= ~(1 << pinindex);
}
mmios_[block->mmio_index].Write32(regval, block->output_offset * sizeof(uint32_t));
}
return ZX_OK;
}
zx_status_t AmlAxgGpio::GpioImplGetInterrupt(uint32_t pin, uint32_t flags, zx::interrupt* out_irq) {
zx_status_t status = ZX_OK;
if (pin > kMaxGpioIndex) {
return ZX_ERR_INVALID_ARGS;
}
fbl::AutoLock al(&irq_lock_);
uint32_t index = GetUnusedIrqIndex(irq_status_);
if (index > info_.irq_count) {
zxlogf(ERROR, "No free IRQ indicies %u, irq_count = %u", (int)index, (int)info_.irq_count);
return ZX_ERR_NO_RESOURCES;
}
for (uint32_t i = 0; i < info_.irq_count; i++) {
if (irq_info_[i] == pin) {
zxlogf(ERROR, "GPIO Interrupt already configured for this pin %u", (int)index);
return ZX_ERR_ALREADY_EXISTS;
}
}
zxlogf(DEBUG, "GPIO Interrupt index %d allocated", (int)index);
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(pin, &block, &pinindex)) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::GpioImplGetInterrupt: pin not found %u", pin);
return status;
}
uint32_t flags_ = flags;
if (flags == ZX_INTERRUPT_MODE_EDGE_LOW) {
// GPIO controller sets the polarity
flags_ = ZX_INTERRUPT_MODE_EDGE_HIGH;
} else if (flags == ZX_INTERRUPT_MODE_LEVEL_LOW) {
flags_ = ZX_INTERRUPT_MODE_LEVEL_HIGH;
}
{
fbl::AutoLock al(&mmio_lock_);
// Configure GPIO Interrupt EDGE and Polarity
uint32_t mode_reg_val =
mmio_interrupt_.Read32(gpio_interrupt_->edge_polarity_offset * sizeof(uint32_t));
switch (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 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((pin - block->pin_block) + block->pin_start, pin_select_index,
kBitsPerGpioInterrupt, pin_select_offset * sizeof(uint32_t));
}
// Create Interrupt Object
if ((status = pdev_.GetInterrupt(index, flags_, out_irq)) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::GpioImplGetInterrupt: pdev_get_interrupt failed %d", status);
return status;
}
irq_status_ |= static_cast<uint8_t>(1 << index);
irq_info_[index] = static_cast<uint16_t>(pin);
return status;
}
zx_status_t AmlAxgGpio::GpioImplReleaseInterrupt(uint32_t pin) {
fbl::AutoLock al(&irq_lock_);
for (uint32_t i = 0; i < info_.irq_count; i++) {
if (irq_info_[i] == pin) {
irq_status_ &= static_cast<uint8_t>(~(1 << i));
irq_info_[i] = kMaxGpioIndex + 1;
return ZX_OK;
}
}
return ZX_ERR_NOT_FOUND;
}
zx_status_t AmlAxgGpio::GpioImplSetPolarity(uint32_t pin, uint32_t polarity) {
int irq_index = -1;
if (pin > kMaxGpioIndex) {
return ZX_ERR_INVALID_ARGS;
}
fbl::AutoLock al(&irq_lock_);
for (uint32_t i = 0; i < info_.irq_count; i++) {
if (irq_info_[i] == pin) {
irq_index = i;
break;
}
}
if (irq_index == -1) {
return ZX_ERR_NOT_FOUND;
}
{
fbl::AutoLock al(&mmio_lock_);
// Configure GPIO Interrupt EDGE and Polarity
if (polarity) {
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));
}
}
return ZX_OK;
}
zx_status_t AmlAxgGpio::GpioImplSetDriveStrength(uint32_t pin, uint64_t ua,
uint64_t* out_actual_ua) {
zx_status_t status;
if (info_.pid == PDEV_PID_AMLOGIC_A113) {
return ZX_ERR_NOT_SUPPORTED;
}
const AmlGpioBlock* block;
uint32_t pinindex;
if ((status = AmlPinToBlock(pin, &block, &pinindex)) != ZX_OK) {
zxlogf(ERROR, "AmlAxgGpio::GpioImplSetDriveStrength: pin not found %u", pin);
return status;
}
DriveStrength ds_val = DRV_4000UA;
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 {
zxlogf(ERROR,
"AmlAxgGpio::GpioImplSetDriveStrength: invalid drive strength %lu, default to 4000 uA\n",
ua);
ds_val = DRV_4000UA;
ua = 4000;
}
pinindex = pin - 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);
{
fbl::AutoLock al(&mmio_lock_);
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));
}
if (out_actual_ua) {
*out_actual_ua = ua;
}
return ZX_OK;
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = AmlAxgGpio::Create;
return ops;
}();
} // namespace gpio
ZIRCON_DRIVER(aml_gpio, gpio::driver_ops, "zircon", "0.1");