blob: 1a076416d567fed00431953a5a32732d71f59202 [file] [log] [blame]
// 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 "mtk-clk.h"
#include <ddk/protocol/platform/bus.h>
#include <ddk/protocol/platform/device.h>
#include <ddktl/pdev.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/unique_ptr.h>
#include <hwreg/bitfields.h>
#include <soc/mt8167/mt8167-clk.h>
#include <zircon/device/clk.h>
namespace clk {
struct MtkClkGateRegs {
const zx_off_t set;
const zx_off_t clr;
};
struct MtkClkGate {
const MtkClkGateRegs regs;
const uint8_t bit;
};
constexpr MtkClkGateRegs kClkGatingCtrl0 = {.set = 0x50, .clr = 0x80};
constexpr MtkClkGateRegs kClkGatingCtrl1 = {.set = 0x54, .clr = 0x84};
constexpr MtkClkGateRegs kClkGatingCtrl8 = {.set = 0xa0, .clr = 0xb0};
constexpr MtkClkGate kMtkClkGates[] = {
[board_mt8167::kClkThermal] = {.regs = kClkGatingCtrl1, .bit = 1},
[board_mt8167::kClkI2c0] = {.regs = kClkGatingCtrl1, .bit = 3},
[board_mt8167::kClkI2c1] = {.regs = kClkGatingCtrl1, .bit = 4},
[board_mt8167::kClkI2c2] = {.regs = kClkGatingCtrl1, .bit = 16},
[board_mt8167::kClkPmicWrapAp] = {.regs = kClkGatingCtrl1, .bit = 20},
[board_mt8167::kClkPmicWrap26M] = {.regs = kClkGatingCtrl1, .bit = 29},
[board_mt8167::kClkAuxAdc] = {.regs = kClkGatingCtrl1, .bit = 30},
[board_mt8167::kClkSlowMfg] = {.regs = kClkGatingCtrl8, .bit = 7},
[board_mt8167::kClkAxiMfg] = {.regs = kClkGatingCtrl8, .bit = 6},
[board_mt8167::kClkMfgMm] = {.regs = kClkGatingCtrl0, .bit = 2},
[board_mt8167::kClkAud1] = {.regs = kClkGatingCtrl8, .bit = 8},
[board_mt8167::kClkAud2] = {.regs = kClkGatingCtrl8, .bit = 9},
[board_mt8167::kClkAudEngen1] = {.regs = kClkGatingCtrl8, .bit = 10},
[board_mt8167::kClkAudEngen2] = {.regs = kClkGatingCtrl8, .bit = 11},
};
struct clock_info {
uint32_t idx;
const char* name;
};
static struct clock_info clks[] = {
{.idx = 1, .name = "mainpll_div8"},
{.idx = 2, .name = "mainpll_div11"},
{.idx = 3, .name = "mainpll_div12"},
{.idx = 4, .name = "mainpll_div20"},
{.idx = 5, .name = "mainpll_div7"},
{.idx = 6, .name = "univpll_div16"},
{.idx = 7, .name = "univpll_div24"},
{.idx = 8, .name = "nfix2"},
{.idx = 9, .name = "whpll"},
{.idx = 10, .name = "wpll"},
{.idx = 11, .name = "26mhz"},
{.idx = 18, .name = "mfg"},
{.idx = 45, .name = "axi_mfg"},
{.idx = 46, .name = "slow_mfg"},
{.idx = 47, .name = "aud1"},
{.idx = 48, .name = "aud2"},
{.idx = 49, .name = "aud engen1"},
{.idx = 50, .name = "aud engen2"},
{.idx = 67, .name = "mmpll"},
{.idx = 69, .name = "aud1pll"},
{.idx = 70, .name = "aud2pll"},
};
namespace {
class FrequencyMeterControl : public hwreg::RegisterBase<FrequencyMeterControl, uint32_t> {
public:
enum {
kFixClk26Mhz = 0,
kFixClk32Khz = 2,
};
DEF_FIELD(29, 28, ck_div);
DEF_FIELD(24, 24, fixclk_sel);
DEF_FIELD(22, 16, monclk_sel);
DEF_BIT(15, enable);
DEF_BIT(14, reset);
DEF_FIELD(11, 0, window);
static auto Get() { return hwreg::RegisterAddr<FrequencyMeterControl>(0x10); }
};
} // namespace
zx_status_t MtkClk::Bind() {
zx_status_t status;
pbus_protocol_t pbus;
status = device_get_protocol(parent(), ZX_PROTOCOL_PBUS, &pbus);
if (status != ZX_OK) {
zxlogf(ERROR, "MtkClk: failed to get ZX_PROTOCOL_PBUS, st = %d\n", status);
return status;
}
clk_protocol_t clk_proto = {
.ops = &clk_protocol_ops_,
.ctx = this,
};
const platform_proxy_cb_t kCallback = {nullptr, nullptr};
status = pbus_register_protocol(&pbus, ZX_PROTOCOL_CLK, &clk_proto, sizeof(clk_proto),
&kCallback);
if (status != ZX_OK) {
zxlogf(ERROR, "MtkClk::Create: pbus_register_protocol failed, st = %d\n", status);
return status;
}
return DdkAdd("mtk-clk");
}
zx_status_t MtkClk::Create(zx_device_t* parent) {
zx_status_t status;
pdev_protocol_t pdev_proto;
if ((status = device_get_protocol(parent, ZX_PROTOCOL_PDEV, &pdev_proto)) != ZX_OK) {
zxlogf(ERROR, "%s: ZX_PROTOCOL_PDEV not available\n", __FILE__);
return status;
}
ddk::PDev pdev(&pdev_proto);
std::optional<ddk::MmioBuffer> mmio;
if ((status = pdev.MapMmio(0, &mmio)) != ZX_OK) {
zxlogf(ERROR, "%s: pdev_map_mmio_buffer failed\n", __FILE__);
return status;
}
fbl::AllocChecker ac;
fbl::unique_ptr<MtkClk> device(new (&ac) MtkClk(parent, *std::move(mmio)));
if (!ac.check()) {
zxlogf(ERROR, "%s: MtkClk alloc failed\n", __FILE__);
return ZX_ERR_NO_MEMORY;
}
if ((status = device->Bind()) != ZX_OK) {
zxlogf(ERROR, "%s: MtkClk bind failed: %d\n", __FILE__, status);
return status;
}
__UNUSED auto* dummy = device.release();
return ZX_OK;
}
zx_status_t MtkClk::ClkEnable(uint32_t index) {
if (index >= fbl::count_of(kMtkClkGates)) {
return ZX_ERR_INVALID_ARGS;
}
const MtkClkGate& gate = kMtkClkGates[index];
mmio_.Write32(1 << gate.bit, gate.regs.clr);
return ZX_OK;
}
zx_status_t MtkClk::ClkDisable(uint32_t index) {
if (index >= fbl::count_of(kMtkClkGates)) {
return ZX_ERR_INVALID_ARGS;
}
const MtkClkGate& gate = kMtkClkGates[index];
mmio_.Write32(1 << gate.bit, gate.regs.set);
return ZX_OK;
}
zx_status_t MtkClk::ClkMeasure(uint32_t clk, clk_freq_info_t* info) {
if (clk >= fbl::count_of(clks)) {
return ZX_ERR_INVALID_ARGS;
}
size_t max_len = sizeof(info->clk_name);
size_t len = strnlen(clks[clk].name, max_len);
if (len == max_len) {
return ZX_ERR_INVALID_ARGS;
}
memcpy(info->clk_name, clks[clk].name, len + 1);
constexpr uint32_t kWindowSize = 512;
constexpr uint32_t kFixedClockFreqMHz = 26000000 / 1000000;
FrequencyMeterControl::Get().FromValue(0).set_reset(true).WriteTo(&mmio_);
FrequencyMeterControl::Get().FromValue(0).set_reset(false).WriteTo(&mmio_);
auto ctrl = FrequencyMeterControl::Get().FromValue(0);
ctrl.set_window(kWindowSize - 1).set_monclk_sel(clks[clk].idx);
ctrl.set_fixclk_sel(FrequencyMeterControl::kFixClk26Mhz).set_enable(true);
ctrl.WriteTo(&mmio_);
hw_wmb();
// Sleep at least kWindowSize ticks of the fixed clock.
zx_nanosleep(zx_deadline_after(ZX_USEC(30)));
// Assume it completed calculating.
constexpr uint32_t kFrequencyMeterReadData = 0x14;
uint32_t count = mmio_.Read32(kFrequencyMeterReadData);
info->clk_freq = (count * kFixedClockFreqMHz) / kWindowSize;
FrequencyMeterControl::Get().FromValue(0).set_reset(true).WriteTo(&mmio_);
FrequencyMeterControl::Get().FromValue(0).set_reset(false).WriteTo(&mmio_);
return ZX_OK;
}
zx_status_t MtkClk::DdkIoctl(uint32_t op, const void* in_buf,
size_t in_len, void* out_buf,
size_t out_len, size_t* out_actual) {
switch (op) {
case IOCTL_CLK_MEASURE: {
if (in_buf == nullptr || in_len != sizeof(uint32_t) ||
out_buf == nullptr || out_len != sizeof(clk_freq_info_t)) {
return ZX_ERR_INVALID_ARGS;
}
auto index = *(static_cast<const uint32_t*>(in_buf));
auto* info = static_cast<clk_freq_info_t*>(out_buf);
*out_actual = sizeof(clk_freq_info_t);
return ClkMeasure(index, info);
}
case IOCTL_CLK_GET_COUNT: {
if (out_buf == nullptr || out_len != sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
}
auto* num_count = static_cast<uint32_t*>(out_buf);
*num_count = static_cast<uint32_t>(fbl::count_of(clks));
*out_actual = sizeof(uint32_t);
return ZX_OK;
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
} // namespace clk
extern "C" zx_status_t mtk_clk_bind(void* ctx, zx_device_t* parent) {
return clk::MtkClk::Create(parent);
}