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