]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/arm/allwinner/clk/aw_hdmiclk.c
Update compiler-rt to release_39 branch r288513. Since this contains a
[FreeBSD/FreeBSD.git] / sys / arm / allwinner / clk / aw_hdmiclk.c
1 /*-
2  * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  */
28
29 /*
30  * Allwinner HDMI clock
31  */
32
33 #include <sys/cdefs.h>
34 __FBSDID("$FreeBSD$");
35
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 #include <sys/bus.h>
39 #include <sys/rman.h>
40 #include <sys/kernel.h>
41 #include <sys/module.h>
42 #include <machine/bus.h>
43
44 #include <dev/ofw/ofw_bus.h>
45 #include <dev/ofw/ofw_bus_subr.h>
46 #include <dev/ofw/ofw_subr.h>
47
48 #include <dev/extres/clk/clk_mux.h>
49 #include <dev/extres/clk/clk_gate.h>
50
51 #include "clkdev_if.h"
52
53 #define SCLK_GATING             (1 << 31)
54 #define CLK_SRC_SEL             (0x3 << 24)
55 #define CLK_SRC_SEL_SHIFT       24
56 #define CLK_SRC_SEL_MAX         0x3
57 #define CLK_RATIO_N             (0x3 << 16)
58 #define CLK_RATIO_N_SHIFT       16
59 #define CLK_RATIO_N_MAX         0x3
60 #define CLK_RATIO_M             (0x1f << 0)
61 #define CLK_RATIO_M_SHIFT       0
62 #define CLK_RATIO_M_MAX         0x1f
63
64 #define CLK_IDX_PLL3_1X         0
65
66 static struct ofw_compat_data compat_data[] = {
67         { "allwinner,sun4i-a10-hdmi-clk",       1 },
68         { NULL, 0 }
69 };
70
71 struct aw_hdmiclk_sc {
72         device_t        clkdev;
73         bus_addr_t      reg;
74 };
75
76 #define HDMICLK_READ(sc, val)   CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val))
77 #define HDMICLK_WRITE(sc, val)  CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val))
78 #define DEVICE_LOCK(sc)         CLKDEV_DEVICE_LOCK((sc)->clkdev)
79 #define DEVICE_UNLOCK(sc)       CLKDEV_DEVICE_UNLOCK((sc)->clkdev)
80
81 static int
82 aw_hdmiclk_init(struct clknode *clk, device_t dev)
83 {
84         struct aw_hdmiclk_sc *sc;
85         uint32_t val, index;
86
87         sc = clknode_get_softc(clk);
88
89         /* Select PLL3(1X) clock source */
90         index = CLK_IDX_PLL3_1X;
91
92         DEVICE_LOCK(sc);
93         HDMICLK_READ(sc, &val);
94         val &= ~CLK_SRC_SEL;
95         val |= (index << CLK_SRC_SEL_SHIFT);
96         HDMICLK_WRITE(sc, val);
97         DEVICE_UNLOCK(sc);
98
99         clknode_init_parent_idx(clk, index);
100         return (0);
101 }
102
103 static int
104 aw_hdmiclk_set_mux(struct clknode *clk, int index)
105 {
106         struct aw_hdmiclk_sc *sc;
107         uint32_t val;
108
109         sc = clknode_get_softc(clk);
110
111         if (index < 0 || index > CLK_SRC_SEL_MAX)
112                 return (ERANGE);
113
114         DEVICE_LOCK(sc);
115         HDMICLK_READ(sc, &val);
116         val &= ~CLK_SRC_SEL;
117         val |= (index << CLK_SRC_SEL_SHIFT);
118         HDMICLK_WRITE(sc, val);
119         DEVICE_UNLOCK(sc);
120
121         return (0);
122 }
123
124 static int
125 aw_hdmiclk_set_gate(struct clknode *clk, bool enable)
126 {
127         struct aw_hdmiclk_sc *sc;
128         uint32_t val;
129
130         sc = clknode_get_softc(clk);
131
132         DEVICE_LOCK(sc);
133         HDMICLK_READ(sc, &val);
134         if (enable)
135                 val |= SCLK_GATING;
136         else
137                 val &= ~SCLK_GATING;
138         HDMICLK_WRITE(sc, val);
139         DEVICE_UNLOCK(sc);
140
141         return (0);
142 }
143
144 static int
145 aw_hdmiclk_recalc_freq(struct clknode *clk, uint64_t *freq)
146 {
147         struct aw_hdmiclk_sc *sc;
148         uint32_t val, m, n;
149
150         sc = clknode_get_softc(clk);
151
152         DEVICE_LOCK(sc);
153         HDMICLK_READ(sc, &val);
154         DEVICE_UNLOCK(sc);
155
156         n = 1 << ((val & CLK_RATIO_N) >> CLK_RATIO_N_SHIFT);
157         m = ((val & CLK_RATIO_M) >> CLK_RATIO_M_SHIFT) + 1;
158
159         *freq = *freq / n / m;
160
161         return (0);
162 }
163
164 static int
165 aw_hdmiclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
166     int flags, int *stop)
167 {
168         struct aw_hdmiclk_sc *sc;
169         uint32_t val, m, n, best_m, best_n;
170         uint64_t cur_freq;
171         int64_t best_diff, cur_diff;
172
173         sc = clknode_get_softc(clk);
174         best_n = best_m = 0;
175         best_diff = (int64_t)*fout; 
176
177         for (n = 0; n <= CLK_RATIO_N_MAX; n++)
178                 for (m = 0; m <= CLK_RATIO_M_MAX; m++) {
179                         cur_freq = fin / (1 << n) / (m + 1);
180                         cur_diff = (int64_t)*fout - cur_freq;
181                         if (cur_diff >= 0 && cur_diff < best_diff) {
182                                 best_diff = cur_diff;
183                                 best_m = m;
184                                 best_n = n;
185                         }
186                 }
187
188         if (best_diff == (int64_t)*fout)
189                 return (ERANGE);
190
191         DEVICE_LOCK(sc);
192         HDMICLK_READ(sc, &val);
193         val &= ~(CLK_RATIO_N | CLK_RATIO_M);
194         val |= (best_n << CLK_RATIO_N_SHIFT);
195         val |= (best_m << CLK_RATIO_M_SHIFT);
196         HDMICLK_WRITE(sc, val);
197         DEVICE_UNLOCK(sc);
198
199         *fout = fin / (1 << best_n) / (best_m + 1);
200         *stop = 1;
201
202         return (0);
203 }
204
205 static clknode_method_t aw_hdmiclk_clknode_methods[] = {
206         /* Device interface */
207         CLKNODEMETHOD(clknode_init,             aw_hdmiclk_init),
208         CLKNODEMETHOD(clknode_set_gate,         aw_hdmiclk_set_gate),
209         CLKNODEMETHOD(clknode_set_mux,          aw_hdmiclk_set_mux),
210         CLKNODEMETHOD(clknode_recalc_freq,      aw_hdmiclk_recalc_freq),
211         CLKNODEMETHOD(clknode_set_freq,         aw_hdmiclk_set_freq),
212         CLKNODEMETHOD_END
213 };
214 DEFINE_CLASS_1(aw_hdmiclk_clknode, aw_hdmiclk_clknode_class,
215     aw_hdmiclk_clknode_methods, sizeof(struct aw_hdmiclk_sc), clknode_class);
216
217 static int
218 aw_hdmiclk_probe(device_t dev)
219 {
220         if (!ofw_bus_status_okay(dev))
221                 return (ENXIO);
222
223         if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
224                 return (ENXIO);
225
226         device_set_desc(dev, "Allwinner HDMI Clock");
227         return (BUS_PROBE_DEFAULT);
228 }
229
230 static int
231 aw_hdmiclk_attach(device_t dev)
232 {
233         struct clknode_init_def def;
234         struct aw_hdmiclk_sc *sc;
235         struct clkdom *clkdom;
236         struct clknode *clk;
237         clk_t clk_parent;
238         bus_addr_t paddr;
239         bus_size_t psize;
240         phandle_t node;
241         int error;
242
243         node = ofw_bus_get_node(dev);
244
245         if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) {
246                 device_printf(dev, "cannot parse 'reg' property\n");
247                 return (ENXIO);
248         }
249
250         clkdom = clkdom_create(dev);
251
252         error = clk_get_by_ofw_index(dev, 0, 0, &clk_parent);
253         if (error != 0) {
254                 device_printf(dev, "cannot parse clock parent\n");
255                 return (ENXIO);
256         }
257
258         memset(&def, 0, sizeof(def));
259         error = clk_parse_ofw_clk_name(dev, node, &def.name);
260         if (error != 0) {
261                 device_printf(dev, "cannot parse clock name\n");
262                 error = ENXIO;
263                 goto fail;
264         }
265         def.id = 1;
266         def.parent_names = malloc(sizeof(char *), M_OFWPROP, M_WAITOK);
267         def.parent_names[0] = clk_get_name(clk_parent);
268         def.parent_cnt = 1;
269
270         clk = clknode_create(clkdom, &aw_hdmiclk_clknode_class, &def);
271         if (clk == NULL) {
272                 device_printf(dev, "cannot create clknode\n");
273                 error = ENXIO;
274                 goto fail;
275         }
276
277         sc = clknode_get_softc(clk);
278         sc->reg = paddr;
279         sc->clkdev = device_get_parent(dev);
280
281         clknode_register(clkdom, clk);
282
283         if (clkdom_finit(clkdom) != 0) {
284                 device_printf(dev, "cannot finalize clkdom initialization\n");
285                 error = ENXIO;
286                 goto fail;
287         }
288
289         if (bootverbose)
290                 clkdom_dump(clkdom);
291
292         return (0);
293
294 fail:
295         return (error);
296 }
297
298 static device_method_t aw_hdmiclk_methods[] = {
299         /* Device interface */
300         DEVMETHOD(device_probe,         aw_hdmiclk_probe),
301         DEVMETHOD(device_attach,        aw_hdmiclk_attach),
302
303         DEVMETHOD_END
304 };
305
306 static driver_t aw_hdmiclk_driver = {
307         "aw_hdmiclk",
308         aw_hdmiclk_methods,
309         0
310 };
311
312 static devclass_t aw_hdmiclk_devclass;
313
314 EARLY_DRIVER_MODULE(aw_hdmiclk, simplebus, aw_hdmiclk_driver,
315     aw_hdmiclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);