| // 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 "aml-clk.h" |
| #include "aml-axg-blocks.h" |
| #include "aml-g12a-blocks.h" |
| #include "aml-g12b-blocks.h" |
| #include "aml-gxl-blocks.h" |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/platform-defs.h> |
| #include <ddk/protocol/platform/bus.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/unique_ptr.h> |
| #include <fuchsia/hardware/clock/c/fidl.h> |
| #include <string.h> |
| |
| namespace amlogic_clock { |
| |
| // MMIO Indexes |
| static constexpr uint32_t kHiuMmio = 0; |
| static constexpr uint32_t kMsrClk = 1; |
| |
| #define MSR_WAIT_BUSY_RETRIES 5 |
| #define MSR_WAIT_BUSY_TIMEOUT_US 10000 |
| |
| zx_status_t AmlClock::InitHiuRegs(pdev_device_info_t* info) { |
| // Map the HIU registers. |
| mmio_buffer_t mmio; |
| zx_status_t status = pdev_map_mmio_buffer(&pdev_, kHiuMmio, ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| &mmio); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "aml-clk: could not map periph mmio: %d\n", status); |
| return status; |
| } |
| hiu_mmio_ = ddk::MmioBuffer(mmio); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlClock::InitMsrRegs(pdev_device_info_t* info) { |
| // Map the MSR registers. |
| mmio_buffer_t mmio; |
| zx_status_t status = pdev_map_mmio_buffer(&pdev_, kMsrClk, ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| &mmio); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "aml-clk: could not map periph mmio: %d\n", status); |
| return status; |
| } |
| msr_mmio_ = ddk::MmioBuffer(mmio); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlClock::InitPdev(zx_device_t* parent) { |
| zx_status_t status = device_get_protocol(parent, |
| ZX_PROTOCOL_PDEV, |
| &pdev_); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Get the device information. |
| pdev_device_info_t info; |
| status = pdev_get_device_info(&pdev_, &info); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "aml-clk: pdev_get_device_info failed\n"); |
| return status; |
| } |
| |
| status = InitHiuRegs(&info); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // If there are more than 1 MMIO range, then this board also |
| // has the clock measure hw block. So we check here if it's |
| // available and map it only if it exists. |
| if (info.mmio_count > 1) { |
| // Map the CLK MSR registers. |
| status = InitMsrRegs(&info); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| |
| meson_clk_gate_t* clk_gates; |
| |
| // Populate the correct register blocks. |
| switch (info.did) { |
| case PDEV_DID_AMLOGIC_AXG_CLK: { |
| clk_gates = (meson_clk_gate_t*)calloc(fbl::count_of(axg_clk_gates), |
| sizeof(meson_clk_gate_t)); |
| if (clk_gates == nullptr) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| memcpy(clk_gates, axg_clk_gates, sizeof(meson_clk_gate_t) * fbl::count_of(axg_clk_gates)); |
| |
| gates_.reset(clk_gates, fbl::count_of(axg_clk_gates)); |
| clk_msr_ = false; |
| break; |
| } |
| case PDEV_DID_AMLOGIC_GXL_CLK: { |
| clk_gates = (meson_clk_gate_t*)calloc(fbl::count_of(gxl_clk_gates), |
| sizeof(meson_clk_gate_t)); |
| if (clk_gates == nullptr) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| memcpy(clk_gates, gxl_clk_gates, sizeof(meson_clk_gate_t) * fbl::count_of(axg_clk_gates)); |
| |
| gates_.reset(clk_gates, fbl::count_of(gxl_clk_gates)); |
| clk_msr_ = false; |
| break; |
| } |
| case PDEV_DID_AMLOGIC_G12A_CLK: { |
| clk_msr_offsets_ = g12a_clk_msr; |
| clk_table_.reset(g12a_clk_table, fbl::count_of(g12a_clk_table)); |
| clk_gates = (meson_clk_gate_t*)calloc(fbl::count_of(g12a_clk_gates), |
| sizeof(meson_clk_gate_t)); |
| if (clk_gates == nullptr) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| memcpy(clk_gates, g12a_clk_gates, sizeof(meson_clk_gate_t) * fbl::count_of(g12a_clk_gates)); |
| |
| gates_.reset(clk_gates, fbl::count_of(g12a_clk_gates)); |
| break; |
| } |
| case PDEV_DID_AMLOGIC_G12B_CLK: { |
| clk_msr_offsets_ = g12b_clk_msr; |
| clk_table_.reset(g12b_clk_table, fbl::count_of(g12b_clk_table)); |
| clk_gates = (meson_clk_gate_t*)calloc(fbl::count_of(g12b_clk_gates), |
| sizeof(meson_clk_gate_t)); |
| if (clk_gates == nullptr) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| memcpy(clk_gates, g12b_clk_gates, sizeof(meson_clk_gate_t) * fbl::count_of(g12b_clk_gates)); |
| |
| gates_.reset(clk_gates, fbl::count_of(g12b_clk_gates)); |
| break; |
| } |
| default: |
| zxlogf(ERROR, "aml-clk: Unsupported SOC DID %u\n", info.pid); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| pbus_protocol_t pbus; |
| status = device_get_protocol(parent, ZX_PROTOCOL_PBUS, &pbus); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "aml-clk: failed to get ZX_PROTOCOL_PBUS, st = %d\n", |
| status); |
| return status; |
| } |
| |
| clock_protocol_t clk_proto = { |
| .ops = &clock_protocol_ops_, |
| .ctx = this, |
| }; |
| |
| status = pbus_register_protocol(&pbus, ZX_PROTOCOL_CLOCK, &clk_proto, sizeof(clk_proto)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "meson_clk_bind: pbus_register_protocol failed, st = %d\n", status); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlClock::Create(zx_device_t* parent) { |
| auto clock_device = fbl::make_unique<amlogic_clock::AmlClock>(parent); |
| |
| zx_status_t status = clock_device->InitPdev(parent); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = clock_device->DdkAdd("clocks"); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "aml-clk: Could not create clock device: %d\n", status); |
| return status; |
| } |
| |
| // devmgr is now in charge of the memory for dev. |
| __UNUSED auto ptr = clock_device.release(); |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlClock::ClkToggle(uint32_t clk, const bool enable) { |
| if (clk >= gates_.size()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| const meson_clk_gate_t* gate = &gates_[clk]; |
| fbl::AutoLock al(&lock_); |
| |
| if (enable) { |
| hiu_mmio_->SetBits32(1 << gate->bit, gate->reg); |
| } else { |
| hiu_mmio_->ClearBits32(1 << gate->bit, gate->reg); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlClock::ClockEnable(uint32_t clk) { |
| if (clk_gates_) { |
| return ClkToggle(clk, true); |
| } else { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| zx_status_t AmlClock::ClockDisable(uint32_t clk) { |
| if (clk_gates_) { |
| return ClkToggle(clk, false); |
| } else { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| // Note: The clock index taken here are the index of clock |
| // from the clock table and not the clock_gates index. |
| // This API measures the clk frequency for clk. |
| // Following implementation is adopted from Amlogic SDK, |
| // there is absolutely no documentation. |
| zx_status_t AmlClock::ClkMeasureUtil(uint32_t clk, uint64_t* clk_freq) { |
| // Set the measurement gate to 64uS. |
| uint32_t value = 64 - 1; |
| msr_mmio_->Write32(value, clk_msr_offsets_.reg0_offset); |
| // Disable continuous measurement. |
| // Disable interrupts. |
| value = MSR_CONT | MSR_INTR; |
| // Clear the clock source. |
| value |= MSR_CLK_SRC_MASK << MSR_CLK_SRC_SHIFT; |
| msr_mmio_->ClearBits32(value, clk_msr_offsets_.reg0_offset); |
| |
| value = ((clk << MSR_CLK_SRC_SHIFT) | // Select the MUX. |
| MSR_RUN | // Enable the clock. |
| MSR_ENABLE); // Enable measuring. |
| msr_mmio_->SetBits32(value, clk_msr_offsets_.reg0_offset); |
| |
| // Wait for the measurement to be done. |
| for (uint32_t i = 0; i < MSR_WAIT_BUSY_RETRIES; i++) { |
| value = msr_mmio_->Read32(clk_msr_offsets_.reg0_offset); |
| if (value & MSR_BUSY) { |
| // Wait a little bit before trying again. |
| zx_nanosleep(zx_deadline_after(ZX_USEC(MSR_WAIT_BUSY_TIMEOUT_US))); |
| continue; |
| } else { |
| // Disable measuring. |
| msr_mmio_->ClearBits32(MSR_ENABLE, clk_msr_offsets_.reg0_offset); |
| // Get the clock value. |
| value = msr_mmio_->Read32(clk_msr_offsets_.reg2_offset); |
| // Magic numbers, since lack of documentation. |
| *clk_freq = (((value + 31) & MSR_VAL_MASK) / 64); |
| return ZX_OK; |
| } |
| } |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| zx_status_t AmlClock::ClkMeasure(uint32_t clk, fuchsia_hardware_clock_FrequencyInfo* info) { |
| if (clk >= clk_table_.size()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| size_t max_len = sizeof(info->name); |
| size_t len = strnlen(clk_table_[clk], max_len); |
| if (len == max_len) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| memcpy(info->name, clk_table_[clk], len + 1); |
| return ClkMeasureUtil(clk, &info->frequency); |
| } |
| |
| uint32_t AmlClock::GetClkCount() { |
| return static_cast<uint32_t>(clk_table_.size()); |
| } |
| |
| void AmlClock::ShutDown() { |
| hiu_mmio_.reset(); |
| msr_mmio_.reset(); |
| } |
| |
| zx_status_t fidl_clk_measure(void* ctx, uint32_t clk, fidl_txn_t* txn) { |
| auto dev = static_cast<AmlClock*>(ctx); |
| fuchsia_hardware_clock_FrequencyInfo info; |
| |
| dev->ClkMeasure(clk, &info); |
| |
| return fuchsia_hardware_clock_DeviceMeasure_reply(txn, &info); |
| } |
| |
| zx_status_t fidl_clk_get_count(void* ctx, fidl_txn_t* txn) { |
| auto dev = static_cast<AmlClock*>(ctx); |
| |
| return fuchsia_hardware_clock_DeviceGetCount_reply(txn, dev->GetClkCount()); |
| } |
| |
| static const fuchsia_hardware_clock_Device_ops_t fidl_ops_ = { |
| .Measure = fidl_clk_measure, |
| .GetCount = fidl_clk_get_count, |
| }; |
| |
| zx_status_t AmlClock::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) { |
| return fuchsia_hardware_clock_Device_dispatch(this, txn, msg, &fidl_ops_); |
| } |
| |
| void AmlClock::DdkUnbind() { |
| ShutDown(); |
| DdkRemove(); |
| } |
| |
| void AmlClock::DdkRelease() { |
| delete this; |
| } |
| |
| } // namespace amlogic_clock |
| |
| zx_status_t aml_clk_bind(void* ctx, zx_device_t* parent) { |
| return amlogic_clock::AmlClock::Create(parent); |
| } |
| |
| static constexpr zx_driver_ops_t aml_clk_driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = aml_clk_bind; |
| return ops; |
| }(); |
| |
| ZIRCON_DRIVER_BEGIN(aml_clk, aml_clk_driver_ops, "zircon", "0.1", 6) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PDEV), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC), |
| // we support multiple SOC variants. |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_AXG_CLK), |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_GXL_CLK), |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_G12A_CLK), |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_G12B_CLK), |
| ZIRCON_DRIVER_END(aml_clk) |