1// Copyright 2018 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <hw/reg.h> 6#include <stdint.h> 7#include <stdlib.h> 8#include <threads.h> 9 10#include <zircon/assert.h> 11#include <zircon/threads.h> 12 13#include <ddk/binding.h> 14#include <ddk/debug.h> 15#include <ddk/device.h> 16#include <ddk/mmio-buffer.h> 17#include <ddk/protocol/clk.h> 18#include <ddk/protocol/platform-bus.h> 19#include <ddk/protocol/platform-defs.h> 20#include <ddk/protocol/platform-device.h> 21 22#include <dev/clk/hisi-lib/hisi.h> 23 24// HiSilicon has two different types of clock gates: 25// 26// + Clock Gates 27// These are enabled and disabled by setting and unsetting bits in the 28// sctrl_mmio register bank. Setting a bit to 1 enables the corresponding 29// clock and 0 disables it. 30// 31// + Separated Clock Gates 32// These are enabled via one bank of registers and disabled via another. 33// Writing 1 to a clock's enable bit will enable it and writing 1 to its 34// disable bank will disable it. 35 36 37// These constants only apply to separated clock gates and correspond to the 38// offset from the register base that needs to be modified to enable/disable 39// the clock. 40#define SEP_ENABLE (0x0) 41#define SEP_DISABLE (0x1) 42#define SEP_STATUS (0x2) 43 44typedef struct hisi_clk { 45 platform_device_protocol_t pdev; 46 clk_protocol_t clk; 47 zx_device_t* zxdev; 48 49 mmio_buffer_t peri_crg_mmio; // Separated Clock Gates 50 mmio_buffer_t sctrl_mmio; // Regular Clock Gates 51 52 hisi_clk_gate_t* gates; 53 size_t gate_count; 54 55 // Serialize access to clocks. 56 mtx_t lock; 57} hisi_clk_t; 58 59static void hisi_sep_clk_toggle_locked(volatile uint8_t* reg, 60 const uint32_t bit, const bool enable) { 61 const uint32_t val = 1 << bit; 62 63 volatile uint32_t* base = (volatile uint32_t*)reg; 64 65 if (enable) { 66 writel(val, base + SEP_ENABLE); 67 } else { 68 writel(val, base + SEP_DISABLE); 69 } 70 71 readl(base + SEP_STATUS); 72} 73 74static void hisi_gate_clk_toggle_locked(volatile uint8_t* reg, 75 const uint32_t bit, const bool enable) { 76 uint32_t val = readl(reg); 77 78 if (enable) { 79 val |= 1 << bit; 80 } else { 81 val &= ~(1 << bit); 82 } 83 84 writel(val, reg); 85} 86 87static zx_status_t hisi_clk_toggle(void* ctx, const uint32_t idx, 88 const bool enable) { 89 hisi_clk_t* const hisi_clk = ctx; 90 91 if (idx >= hisi_clk->gate_count) return ZX_ERR_INVALID_ARGS; 92 93 const hisi_clk_gate_t* const gate = &hisi_clk->gates[idx]; 94 95 const uint32_t flags = gate->flags; 96 97 // Select the register bank depending on which bank this clock belongs to. 98 mtx_lock(&hisi_clk->lock); 99 if (HISI_CLK_FLAG_BANK(flags) == HISI_CLK_FLAG_BANK_SCTRL) { 100 volatile uint8_t* base = 101 (volatile uint8_t*)hisi_clk->sctrl_mmio.vaddr; 102 hisi_gate_clk_toggle_locked(base + gate->reg, gate->bit, enable); 103 } else if (HISI_CLK_FLAG_BANK(flags) == HISI_CLK_FLAG_BANK_PERI) { 104 volatile uint8_t* base = 105 (volatile uint8_t*)hisi_clk->peri_crg_mmio.vaddr; 106 hisi_sep_clk_toggle_locked(base + gate->reg, gate->bit, enable); 107 } else { 108 // Maybe you passed an unimplemented clock bank? 109 ZX_DEBUG_ASSERT(false); 110 } 111 mtx_unlock(&hisi_clk->lock); 112 113 return ZX_OK; 114} 115 116static zx_status_t hisi_clk_enable(void* ctx, uint32_t clk) { 117 return hisi_clk_toggle(ctx, clk, true); 118} 119 120static zx_status_t hisi_clk_disable(void* ctx, uint32_t clk) { 121 return hisi_clk_toggle(ctx, clk, false); 122} 123 124clk_protocol_ops_t clk_ops = { 125 .enable = hisi_clk_enable, 126 .disable = hisi_clk_disable, 127}; 128 129static void hisi_clk_release(void* ctx) { 130 hisi_clk_t* clk = ctx; 131 mmio_buffer_release(&clk->peri_crg_mmio); 132 mmio_buffer_release(&clk->sctrl_mmio); 133 mtx_destroy(&clk->lock); 134 free(clk); 135} 136 137 138static zx_protocol_device_t hisi_clk_device_proto = { 139 .version = DEVICE_OPS_VERSION, 140 .release = hisi_clk_release, 141}; 142 143static void hisi_validate_gates(const hisi_clk_gate_t* gates, const size_t n) { 144 // A clock can't exist in both banks. 145 const uint32_t kBadFlagMask = 146 (HISI_CLK_FLAG_BANK_SCTRL | HISI_CLK_FLAG_BANK_PERI); 147 148 for (size_t i = 0; i < n; i++) { 149 ZX_DEBUG_ASSERT(HISI_CLK_FLAG_BANK(gates[i].flags) != kBadFlagMask); 150 } 151} 152 153zx_status_t hisi_clk_init(const char* name, hisi_clk_gate_t* gates, 154 const size_t gate_count, zx_device_t* parent) { 155 zx_status_t st = ZX_ERR_INTERNAL; 156 157 hisi_validate_gates(gates, gate_count); 158 159 hisi_clk_t* hisi_clk = calloc(1, sizeof(*hisi_clk)); 160 if (!hisi_clk) { 161 zxlogf(ERROR, "hisi_clk_bind: failed to allocate hisi_clk, " 162 "st = %d\n", ZX_ERR_NO_MEMORY); 163 return ZX_ERR_NO_MEMORY; 164 } 165 166 hisi_clk->gates = gates; 167 hisi_clk->gate_count = gate_count; 168 169 int ret = mtx_init(&hisi_clk->lock, mtx_plain); 170 if (ret != thrd_success) { 171 st = thrd_status_to_zx_status(ret); 172 zxlogf(ERROR, "hisi_clk_bind: failed to initialize mutex, st = %d\n", 173 st); 174 goto fail; 175 } 176 177 st = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &hisi_clk->pdev); 178 if (st != ZX_OK) { 179 zxlogf(ERROR, "hisi_clk_bind: failed to get ZX_PROTOCOL_PLATFORM_DEV, " 180 "st = %d\n", st); 181 goto fail; 182 } 183 184 platform_bus_protocol_t pbus; 185 st = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_BUS, &pbus); 186 if (st != ZX_OK) { 187 zxlogf(ERROR, "hisi_clk_bind: failed to get ZX_PROTOCOL_PLATFORM_BUS, " 188 "st = %d\n", st); 189 goto fail; 190 } 191 192 // Map in MMIO for separated clock gates. 193 st = pdev_map_mmio_buffer2(&hisi_clk->pdev, 0, 194 ZX_CACHE_POLICY_UNCACHED_DEVICE, 195 &hisi_clk->peri_crg_mmio); 196 if (st != ZX_OK) { 197 zxlogf(ERROR, "hisi_clk_bind: failed to map MMIO_PERI_CRG, st = %d\n", st); 198 goto fail; 199 } 200 201 202 // Map in MMIO for regular clock gates. 203 st = pdev_map_mmio_buffer2(&hisi_clk->pdev, 1, 204 ZX_CACHE_POLICY_UNCACHED_DEVICE, 205 &hisi_clk->sctrl_mmio); 206 if (st != ZX_OK) { 207 zxlogf(ERROR, "hisi_clk_bind: failed to map MMIO_SCTRL, st = %d\n", st); 208 goto fail; 209 } 210 211 device_add_args_t args = { 212 .version = DEVICE_ADD_ARGS_VERSION, 213 .name = name, 214 .ctx = hisi_clk, 215 .ops = &hisi_clk_device_proto, 216 .flags = DEVICE_ADD_NON_BINDABLE, 217 }; 218 219 st = device_add(parent, &args, &hisi_clk->zxdev); 220 if (st != ZX_OK) { 221 zxlogf(ERROR, "hisi_clk_bind: device_add failed, st = %d\n", st); 222 goto fail; 223 } 224 225 hisi_clk->clk.ops = &clk_ops; 226 hisi_clk->clk.ctx = hisi_clk; 227 228 st = pbus_register_protocol(&pbus, ZX_PROTOCOL_CLK, &hisi_clk->clk, NULL, NULL); 229 if (st != ZX_OK) { 230 zxlogf(ERROR, "hisi_clk_bind: pbus_register_protocol failed, st = %d\n", st); 231 goto fail; 232 } 233 234 return ZX_OK; 235 236fail: 237 hisi_clk_release(hisi_clk); 238 239 // Make sure we don't accidentally return ZX_OK if the device has failed 240 // to bind for some reason 241 ZX_DEBUG_ASSERT(st != ZX_OK); 242 return st; 243} 244