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