| // 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 <dev/clk/hisi-lib/hisi-clk.h> |
| |
| #include <hw/reg.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <threads.h> |
| |
| #include <zircon/assert.h> |
| #include <zircon/threads.h> |
| |
| #include <fbl/auto_call.h> |
| #include <fbl/auto_lock.h> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/mmio-buffer.h> |
| #include <ddk/platform-defs.h> |
| #include <ddk/protocol/clockimpl.h> |
| #include <ddk/protocol/platform-device-lib.h> |
| #include <ddk/protocol/platform/device.h> |
| #include <ddktl/pdev.h> |
| |
| #include <ddktl/protocol/platform/bus.h> |
| |
| #include <dev/clk/hisi-lib/hisi-gate.h> |
| |
| // HiSilicon has two different types of clock gates: |
| // |
| // + Clock Gates |
| // These are enabled and disabled by setting and unsetting bits in the |
| // sctrl_mmio register bank. Setting a bit to 1 enables the corresponding |
| // clock and 0 disables it. |
| // |
| // + Separated Clock Gates |
| // These are enabled via one bank of registers and disabled via another. |
| // Writing 1 to a clock's enable bit will enable it and writing 1 to its |
| // disable bank will disable it. |
| |
| // These constants only apply to separated clock gates and correspond to the |
| // offset from the register base that needs to be modified to enable/disable |
| // the clock. |
| namespace { |
| constexpr size_t kSepEnable = 0; |
| constexpr size_t kSepDisable = 4; |
| constexpr size_t kSepStatus = 8; |
| } // namespace |
| |
| namespace hisi_clock { |
| |
| zx_status_t HisiClock::Create(const char* name, const Gate gate_list[], |
| const size_t gate_count, zx_device_t* parent) { |
| zx_status_t st; |
| |
| // Avoiding make_unique becuase HisiClock has a private constructor. |
| std::unique_ptr<HisiClock> device(new HisiClock(parent, gate_list, gate_count)); |
| |
| st = device->Init(); |
| if (st != ZX_OK) { |
| zxlogf(ERROR, "HisiClock::Create: failed to init device, rc = %d\n", st); |
| return st; |
| } |
| |
| st = device->DdkAdd(name); |
| if (st != ZX_OK) { |
| zxlogf(ERROR, "HisiClock::Create: failed to add device, rc = %d\n", st); |
| return st; |
| } |
| |
| // Devmgr owns memory now. |
| __UNUSED auto ptr = device.release(); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t HisiClock::ToggleSepClkLocked(const Gate& gate, bool enable) { |
| const uint32_t val = 1 << gate.Bit(); |
| |
| if (enable) { |
| peri_crg_mmio_->Write32(val, gate.Reg() + kSepEnable); |
| } else { |
| peri_crg_mmio_->Write32(val, gate.Reg() + kSepDisable); |
| } |
| |
| peri_crg_mmio_->Read32(gate.Reg() + kSepStatus); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t HisiClock::ToggleGateClkLocked(const Gate& gate, bool enable) { |
| if (enable) { |
| sctrl_mmio_->SetBits32(gate.Bit(), gate.Reg()); |
| } else { |
| sctrl_mmio_->ClearBits32(gate.Bit(), gate.Reg()); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t HisiClock::Toggle(uint32_t clock, bool enable) { |
| fbl::AutoLock<fbl::Mutex> guard(&lock_); |
| |
| if (clock >= gate_count_) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| const Gate& gate = gates_[clock]; |
| |
| switch (gate.Bank()) { |
| case RegisterBank::Sctrl: |
| return ToggleGateClkLocked(gate, enable); |
| case RegisterBank::Peri: |
| return ToggleSepClkLocked(gate, enable); |
| } |
| |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t HisiClock::ClockImplEnable(uint32_t clock) { |
| return Toggle(clock, true); |
| } |
| |
| zx_status_t HisiClock::ClockImplDisable(uint32_t clock) { |
| return Toggle(clock, false); |
| } |
| |
| zx_status_t HisiClock::ClockImplRequestRate(uint32_t id, uint64_t hz) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| void HisiClock::DdkUnbind() { |
| DeInit(); |
| DdkRemove(); |
| } |
| |
| void HisiClock::DdkRelease() { |
| delete this; |
| } |
| |
| void HisiClock::DeInit() { |
| fbl::AutoLock<fbl::Mutex> guard(&lock_); |
| |
| peri_crg_mmio_.reset(); |
| sctrl_mmio_.reset(); |
| } |
| |
| zx_status_t HisiClock::RegisterClockProtocol() { |
| zx_status_t st; |
| |
| ddk::PBusProtocolClient pbus(parent()); |
| if (!pbus.is_valid()) { |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| clock_impl_protocol_t clk_proto = { |
| .ops = &clock_impl_protocol_ops_, |
| .ctx = this, |
| }; |
| |
| st = pbus.RegisterProtocol(ZX_PROTOCOL_CLOCK_IMPL, &clk_proto, sizeof(clk_proto)); |
| if (st != ZX_OK) { |
| zxlogf(ERROR, "HisiClock::RegisterClockProtocol: pbus_register_protocol" |
| " failed with st = %d\n", |
| st); |
| return st; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t HisiClock::Init() { |
| zx_status_t st; |
| |
| ddk::PDev pdev(parent()); |
| if (!pdev.is_valid()) { |
| zxlogf(ERROR, "HisiClock::Init: failed to get pdev protocol\n"); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| st = pdev.MapMmio(0, &peri_crg_mmio_); |
| if (st != ZX_OK) { |
| zxlogf(ERROR, "HisiClock::Init: map peri crg mmio failed, st = %d\n", st); |
| return st; |
| } |
| |
| st = pdev.MapMmio(1, &sctrl_mmio_); |
| if (st != ZX_OK) { |
| zxlogf(ERROR, "HisiClock::Init: map sctrl mmio failed, st = %d\n", st); |
| return st; |
| } |
| |
| st = RegisterClockProtocol(); |
| |
| return ZX_OK; |
| } |
| |
| } // namespace hisi_clock |