| // Copyright 2018 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 <stdint.h> |
| #include <threads.h> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/unique_ptr.h> |
| #include <hw/reg.h> |
| #include <soc/mt8167/mt8167-hw.h> |
| #include <zircon/syscalls/port.h> |
| #include <zircon/types.h> |
| |
| #include "mt8167-gpio.h" |
| |
| namespace gpio { |
| |
| int Mt8167GpioDevice::Thread() { |
| while (1) { |
| zx_port_packet_t packet; |
| zx_status_t status = port_.wait(zx::time::infinite(), &packet); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s port wait failed: %d\n", __FUNCTION__, status); |
| return thrd_error; |
| } |
| uint32_t index = eint_.GetNextInterrupt(0); |
| while (index != ExtendedInterruptReg::kInvalidInterruptIdx && |
| index < interrupts_.size() && |
| interrupts_[index].is_valid()) { |
| zxlogf(TRACE, "%s msg on port key %lu EINT %u\n", __FUNCTION__, packet.key, index); |
| if (eint_.IsEnabled(index)) { |
| zxlogf(TRACE, "%s zx_interrupt_trigger for %u\n", __FUNCTION__, index); |
| status = interrupts_[index].trigger(0, zx::time(packet.interrupt.timestamp)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s zx_interrupt_trigger failed %d \n", __FUNCTION__, status); |
| } |
| } |
| eint_.AckInterrupt(index); |
| index = eint_.GetNextInterrupt(index + 1); |
| } |
| int_.ack(); |
| } |
| } |
| |
| zx_status_t Mt8167GpioDevice::GpioImplConfigIn(uint32_t index, uint32_t flags) { |
| if (index >= interrupts_.size()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| GpioModeReg::SetMode(&gpio_mmio_, index, GpioModeReg::kModeGpio); |
| dir_.SetDir(index, false); |
| const uint32_t pull_mode = flags & GPIO_PULL_MASK; |
| |
| switch (pull_mode) { |
| case GPIO_NO_PULL: |
| if (pull_en_.PullDisable(index)) { |
| return ZX_OK; |
| } |
| break; |
| case GPIO_PULL_UP: |
| if (pull_en_.PullEnable(index) && pull_sel_.SetPullUp(index)) { |
| return ZX_OK; |
| } |
| break; |
| case GPIO_PULL_DOWN: |
| if (pull_en_.PullEnable(index) && pull_sel_.SetPullDown(index)) { |
| return ZX_OK; |
| } |
| break; |
| } |
| |
| // If not supported above, try IO Config. |
| // TODO(andresoportus): We only support enable/disable pull through the GPIO protocol, so |
| // until we allow passing particular pull amounts we can specify here different pull amounts |
| // for particular GPIOs. |
| PullAmount pull_amount = kPull10K; |
| if (index >= 40 && index <= 43) { |
| pull_amount = kPull75K; |
| } |
| switch (pull_mode) { |
| case GPIO_NO_PULL: |
| if (iocfg_.PullDisable(index)) { |
| return ZX_OK; |
| } |
| break; |
| case GPIO_PULL_UP: |
| if (iocfg_.PullEnable(index, pull_amount) && iocfg_.SetPullUp(index)) { |
| return ZX_OK; |
| } |
| break; |
| case GPIO_PULL_DOWN: |
| if (iocfg_.PullEnable(index, pull_amount) && iocfg_.SetPullDown(index)) { |
| return ZX_OK; |
| } |
| break; |
| } |
| |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t Mt8167GpioDevice::GpioImplConfigOut(uint32_t index, uint8_t initial_value) { |
| if (index >= interrupts_.size()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| GpioModeReg::SetMode(&gpio_mmio_, index, GpioModeReg::kModeGpio); |
| dir_.SetDir(index, true); |
| return GpioImplWrite(index, initial_value); |
| } |
| |
| zx_status_t Mt8167GpioDevice::GpioImplSetAltFunction(uint32_t index, uint64_t function) { |
| if (index >= interrupts_.size()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (function >= GpioModeReg::kModeMax) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| GpioModeReg::SetMode(&gpio_mmio_, index, static_cast<uint16_t>(function)); |
| return ZX_OK; |
| } |
| |
| zx_status_t Mt8167GpioDevice::GpioImplRead(uint32_t index, uint8_t* out_value) { |
| if (index >= interrupts_.size()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| *out_value = static_cast<uint8_t>(in_.GetVal(index)); |
| return ZX_OK; |
| } |
| |
| zx_status_t Mt8167GpioDevice::GpioImplWrite(uint32_t index, uint8_t value) { |
| if (index >= interrupts_.size()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| out_.SetVal(index, value); |
| return ZX_OK; |
| } |
| |
| zx_status_t Mt8167GpioDevice::GpioImplGetInterrupt(uint32_t index, uint32_t flags, |
| zx::interrupt* out_irq) { |
| zx_status_t status; |
| if (index >= interrupts_.size()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (eint_.IsEnabled(index)) { |
| zxlogf(ERROR, "%s interrupt %u already exists\n", __FUNCTION__, index); |
| return ZX_ERR_ALREADY_EXISTS; |
| } |
| |
| zx::interrupt irq; |
| status = zx::interrupt::create(zx::resource(), index, ZX_INTERRUPT_VIRTUAL, &irq); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s zx::interrupt::create failed %d \n", __FUNCTION__, status); |
| return status; |
| } |
| status = irq.duplicate(ZX_RIGHT_SAME_RIGHTS, out_irq); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s interrupt.duplicate failed %d \n", __FUNCTION__, status); |
| return status; |
| } |
| |
| switch (flags & ZX_INTERRUPT_MODE_MASK) { |
| case ZX_INTERRUPT_MODE_EDGE_LOW: |
| eint_.SetPolarity(index, false); |
| eint_.SetEdge(index, true); |
| break; |
| case ZX_INTERRUPT_MODE_EDGE_HIGH: |
| eint_.SetPolarity(index, true); |
| eint_.SetEdge(index, true); |
| break; |
| case ZX_INTERRUPT_MODE_LEVEL_LOW: |
| eint_.SetPolarity(index, false); |
| eint_.SetEdge(index, false); |
| break; |
| case ZX_INTERRUPT_MODE_LEVEL_HIGH: |
| eint_.SetPolarity(index, true); |
| eint_.SetEdge(index, false); |
| break; |
| case ZX_INTERRUPT_MODE_EDGE_BOTH: |
| return ZX_ERR_NOT_SUPPORTED; |
| default: |
| return ZX_ERR_INVALID_ARGS; |
| } |
| interrupts_[index] = std::move(irq); |
| eint_.Enable(index); |
| zxlogf(TRACE, "%s EINT %u enabled\n", __FUNCTION__, index); |
| return ZX_OK; |
| } |
| |
| zx_status_t Mt8167GpioDevice::GpioImplReleaseInterrupt(uint32_t index) { |
| if (index >= interrupts_.size() || !eint_.IsEnabled(index)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| eint_.Disable(index); |
| interrupts_[index].destroy(); |
| interrupts_[index].reset(); |
| return ZX_OK; |
| } |
| |
| zx_status_t Mt8167GpioDevice::GpioImplSetPolarity(uint32_t index, uint32_t polarity) { |
| if (index >= interrupts_.size()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (polarity == GPIO_POLARITY_LOW) { |
| eint_.SetPolarity(index, false); |
| return ZX_OK; |
| } else if (polarity == GPIO_POLARITY_HIGH) { |
| eint_.SetPolarity(index, true); |
| return ZX_OK; |
| } |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| void Mt8167GpioDevice::ShutDown() { |
| int_.destroy(); |
| thrd_join(thread_, NULL); |
| } |
| |
| void Mt8167GpioDevice::DdkUnbind() { |
| ShutDown(); |
| DdkRemove(); |
| } |
| |
| void Mt8167GpioDevice::DdkRelease() { |
| delete this; |
| } |
| |
| zx_status_t Mt8167GpioDevice::Bind() { |
| 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 \n", __FUNCTION__, status); |
| return status; |
| } |
| |
| status = pdev_map_interrupt(&pdev, 0, int_.reset_and_get_address()); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s pdev_map_interrupt failed %d\n", __FUNCTION__, status); |
| return status; |
| } |
| |
| status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s zx_port_create failed %d\n", __FUNCTION__, status); |
| return status; |
| } |
| |
| status = int_.bind(port_, 0, 0 /*options*/); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s zx_interrupt_bind failed %d\n", __FUNCTION__, status); |
| return status; |
| } |
| fbl::AllocChecker ac; |
| interrupts_ = fbl::Array(new (&ac) zx::interrupt[MT8167_GPIO_EINT_MAX], MT8167_GPIO_EINT_MAX); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| for (uint32_t i = 0; i < interrupts_.size(); ++i) { |
| eint_.SetDomain0(i); |
| eint_.Disable(i); |
| } |
| |
| auto cb = [](void* arg) -> int { return reinterpret_cast<Mt8167GpioDevice*>(arg)->Thread(); }; |
| int rc = thrd_create_with_name(&thread_, cb, this, "mt8167-gpio-thread"); |
| if (rc != thrd_success) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| status = DdkAdd("mt8167-gpio"); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s DdkAdd failed %d\n", __FUNCTION__, status); |
| ShutDown(); |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Mt8167GpioDevice::Init() { |
| zx_status_t status; |
| pbus_protocol_t pbus; |
| status = device_get_protocol(parent(), ZX_PROTOCOL_PBUS, &pbus); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: ZX_PROTOCOL_PBUS not available %d\n", __FUNCTION__, status); |
| return status; |
| } |
| gpio_impl_protocol_t gpio_proto = { |
| .ops = &gpio_impl_protocol_ops_, |
| .ctx = this, |
| }; |
| const platform_proxy_cb_t kCallback = {nullptr, nullptr}; |
| status = pbus_register_protocol(&pbus, ZX_PROTOCOL_GPIO_IMPL, &gpio_proto, sizeof(gpio_proto), |
| &kCallback); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s pbus_register_protocol failed %d\n", __FUNCTION__, status); |
| ShutDown(); |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Mt8167GpioDevice::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 \n", __FUNCTION__, 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\n", __FUNCTION__, status); |
| return status; |
| } |
| |
| mmio_buffer_t iocfg_mmio; |
| status = pdev_map_mmio_buffer(&pdev, 1, ZX_CACHE_POLICY_UNCACHED_DEVICE, &iocfg_mmio); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s iocfg pdev_map_mmio_buffer failed %d\n", __FUNCTION__, status); |
| return status; |
| } |
| |
| mmio_buffer_t eint_mmio; |
| status = pdev_map_mmio_buffer(&pdev, 0, ZX_CACHE_POLICY_UNCACHED_DEVICE, &eint_mmio); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: pdev_map_mmio_buffer gpio failed %d\n", __FUNCTION__, status); |
| return status; |
| } |
| |
| fbl::AllocChecker ac; |
| auto dev = fbl::make_unique_checked<gpio::Mt8167GpioDevice>(&ac, parent, gpio_mmio, iocfg_mmio, |
| eint_mmio); |
| if (!ac.check()) { |
| zxlogf(ERROR, "mt8167_gpio_bind: ZX_ERR_NO_MEMORY\n"); |
| 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 mt8167_gpio_bind(void* ctx, zx_device_t* parent) { |
| return gpio::Mt8167GpioDevice::Create(parent); |
| } |
| |
| } // namespace gpio |