]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/arm/allwinner/clk/aw_lcdclk.c
Merge in changes from ^/vendor/NetBSD/tests/dist@r313245
[FreeBSD/FreeBSD.git] / sys / arm / allwinner / clk / aw_lcdclk.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 LCD clocks
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.h>
49 #include <dev/extres/hwreset/hwreset.h>
50
51 #include "clkdev_if.h"
52 #include "hwreset_if.h"
53
54 /* CH0 */
55 #define CH0_SCLK_GATING                 (1 << 31)
56 #define CH0_LCD_RST                     (1 << 30)
57 #define CH0_CLK_SRC_SEL                 (0x3 << 24)
58 #define CH0_CLK_SRC_SEL_SHIFT           24
59 #define CH0_CLK_SRC_SEL_PLL3_1X         0
60 #define CH0_CLK_SRC_SEL_PLL7_1X         1
61 #define CH0_CLK_SRC_SEL_PLL3_2X         2
62 #define CH0_CLK_SRC_SEL_PLL6            3
63
64 /* CH1 */
65 #define CH1_SCLK2_GATING                (1 << 31)
66 #define CH1_SCLK2_SEL                   (0x3 << 24)
67 #define CH1_SCLK2_SEL_SHIFT             24
68 #define CH1_SCLK2_SEL_PLL3_1X           0
69 #define CH1_SCLK2_SEL_PLL7_1X           1
70 #define CH1_SCLK2_SEL_PLL3_2X           2
71 #define CH1_SCLK2_SEL_PLL7_2X           3
72 #define CH1_SCLK1_GATING                (1 << 15)
73 #define CH1_SCLK1_SEL                   (0x1 << 11)
74 #define CH1_SCLK1_SEL_SHIFT             11
75 #define CH1_SCLK1_SEL_SCLK2             0
76 #define CH1_SCLK1_SEL_SCLK2_DIV2        1
77 #define CH1_CLK_DIV_RATIO_M             (0x1f << 0)
78 #define CH1_CLK_DIV_RATIO_M_SHIFT       0
79
80 #define TCON_PLLREF                     3000000ULL
81 #define TCON_PLLREF_FRAC1               297000000ULL
82 #define TCON_PLLREF_FRAC2               270000000ULL
83 #define TCON_PLL_M_MIN                  1
84 #define TCON_PLL_M_MAX                  15
85 #define TCON_PLL_N_MIN                  9
86 #define TCON_PLL_N_MAX                  127
87
88 #define CLK_IDX_CH1_SCLK1               0
89 #define CLK_IDX_CH1_SCLK2               1
90
91 #define CLK_IDX_
92
93 enum aw_lcdclk_type {
94         AW_LCD_CH0 = 1,
95         AW_LCD_CH1,
96 };
97
98 static struct ofw_compat_data compat_data[] = {
99         { "allwinner,sun4i-a10-lcd-ch0-clk",    AW_LCD_CH0 },
100         { "allwinner,sun4i-a10-lcd-ch1-clk",    AW_LCD_CH1 },
101         { NULL, 0 }
102 };
103
104 struct aw_lcdclk_softc {
105         enum aw_lcdclk_type     type;
106         device_t                clkdev;
107         bus_addr_t              reg;
108         int                     id;
109 };
110
111 #define LCDCLK_READ(sc, val)    CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val))
112 #define LCDCLK_WRITE(sc, val)   CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val))
113 #define LCDCLK_MODIFY(sc, clr, set)     \
114         CLKDEV_MODIFY_4((sc)->clkdev, (sc)->reg, (clr), (set))
115 #define DEVICE_LOCK(sc)         CLKDEV_DEVICE_LOCK((sc)->clkdev)
116 #define DEVICE_UNLOCK(sc)       CLKDEV_DEVICE_UNLOCK((sc)->clkdev)
117
118 static int
119 aw_lcdclk_hwreset_assert(device_t dev, intptr_t id, bool value)
120 {
121         struct aw_lcdclk_softc *sc;
122         int error;
123
124         sc = device_get_softc(dev);
125
126         if (sc->type != AW_LCD_CH0)
127                 return (ENXIO);
128
129         DEVICE_LOCK(sc);
130         error = LCDCLK_MODIFY(sc, CH0_LCD_RST, value ? 0 : CH0_LCD_RST);
131         DEVICE_UNLOCK(sc);
132
133         return (error);
134 }
135
136 static int
137 aw_lcdclk_hwreset_is_asserted(device_t dev, intptr_t id, bool *value)
138 {
139         struct aw_lcdclk_softc *sc;
140         uint32_t val;
141         int error;
142
143         sc = device_get_softc(dev);
144
145         if (sc->type != AW_LCD_CH0)
146                 return (ENXIO);
147
148         DEVICE_LOCK(sc);
149         error = LCDCLK_READ(sc, &val);
150         DEVICE_UNLOCK(sc);
151
152         if (error)
153                 return (error);
154
155         *value = (val & CH0_LCD_RST) != 0 ? false : true;
156
157         return (0);
158 }
159
160 static int
161 aw_lcdclk_init(struct clknode *clk, device_t dev)
162 {
163         struct aw_lcdclk_softc *sc;
164         uint32_t val, index;
165
166         sc = clknode_get_softc(clk);
167
168         DEVICE_LOCK(sc);
169         LCDCLK_READ(sc, &val);
170         DEVICE_UNLOCK(sc);
171
172         switch (sc->type) {
173         case AW_LCD_CH0:
174                 index = (val & CH0_CLK_SRC_SEL) >> CH0_CLK_SRC_SEL_SHIFT;
175                 break;
176         case AW_LCD_CH1:
177                 switch (sc->id) {
178                 case CLK_IDX_CH1_SCLK1:
179                         index = 0;
180                         break;
181                 case CLK_IDX_CH1_SCLK2:
182                         index = (val & CH1_SCLK2_SEL_SHIFT) >>
183                             CH1_SCLK2_SEL_SHIFT;
184                         break;
185                 default:
186                         return (ENXIO);
187                 }
188                 break;
189         default:
190                 return (ENXIO);
191         }
192
193         clknode_init_parent_idx(clk, index);
194         return (0);
195 }
196
197 static int
198 aw_lcdclk_set_mux(struct clknode *clk, int index)
199 {
200         struct aw_lcdclk_softc *sc;
201         uint32_t val;
202
203         sc = clknode_get_softc(clk);
204
205         switch (sc->type) {
206         case AW_LCD_CH0:
207                 DEVICE_LOCK(sc);
208                 LCDCLK_READ(sc, &val);
209                 val &= ~CH0_CLK_SRC_SEL;
210                 val |= (index << CH0_CLK_SRC_SEL_SHIFT);
211                 LCDCLK_WRITE(sc, val);
212                 DEVICE_UNLOCK(sc);
213                 break;
214         case AW_LCD_CH1:
215                 switch (sc->id) {
216                 case CLK_IDX_CH1_SCLK2:
217                         DEVICE_LOCK(sc);
218                         LCDCLK_READ(sc, &val);
219                         val &= ~CH1_SCLK2_SEL;
220                         val |= (index << CH1_SCLK2_SEL_SHIFT);
221                         LCDCLK_WRITE(sc, val);
222                         DEVICE_UNLOCK(sc);
223                         break;
224                 default:
225                         return (ENXIO);
226                 }
227                 break;
228         default:
229                 return (ENXIO);
230         }
231
232         return (0);
233 }
234
235 static int
236 aw_lcdclk_set_gate(struct clknode *clk, bool enable)
237 {
238         struct aw_lcdclk_softc *sc;
239         uint32_t val, mask;
240
241         sc = clknode_get_softc(clk);
242
243         switch (sc->type) {
244         case AW_LCD_CH0:
245                 mask = CH0_SCLK_GATING;
246                 break;
247         case AW_LCD_CH1:
248                 mask = (sc->id == CLK_IDX_CH1_SCLK1) ? CH1_SCLK1_GATING :
249                     CH1_SCLK2_GATING;
250                 break;
251         default:
252                 return (ENXIO);
253         }
254
255         DEVICE_LOCK(sc);
256         LCDCLK_READ(sc, &val);
257         if (enable)
258                 val |= mask;
259         else
260                 val &= ~mask;
261         LCDCLK_WRITE(sc, val);
262         DEVICE_UNLOCK(sc);
263
264         return (0);
265 }
266
267 static int
268 aw_lcdclk_recalc_freq(struct clknode *clk, uint64_t *freq)
269 {
270         struct aw_lcdclk_softc *sc;
271         uint32_t val, m, src_sel;
272
273         sc = clknode_get_softc(clk);
274
275         if (sc->type != AW_LCD_CH1)
276                 return (0);
277
278         DEVICE_LOCK(sc);
279         LCDCLK_READ(sc, &val);
280         DEVICE_UNLOCK(sc);
281
282         m = ((val & CH1_CLK_DIV_RATIO_M) >> CH1_CLK_DIV_RATIO_M_SHIFT) + 1;
283         *freq = *freq / m;
284
285         if (sc->id == CLK_IDX_CH1_SCLK1) {
286                 src_sel = (val & CH1_SCLK1_SEL) >> CH1_SCLK1_SEL_SHIFT;
287                 if (src_sel == CH1_SCLK1_SEL_SCLK2_DIV2)
288                         *freq /= 2;
289         }
290
291         return (0);
292 }
293
294 static void
295 calc_tcon_pll_integer(uint64_t fin, uint64_t fout, uint32_t *pm, uint32_t *pn)
296 {
297         int64_t diff, fcur, best;
298         int m, n;
299
300         best = fout;
301         for (m = TCON_PLL_M_MIN; m <= TCON_PLL_M_MAX; m++) {
302                 for (n = TCON_PLL_N_MIN; n <= TCON_PLL_N_MAX; n++) {
303                         fcur = (n * fin) / m;
304                         diff = (int64_t)fout - fcur;
305                         if (diff > 0 && diff < best) {
306                                 best = diff;
307                                 *pm = m;
308                                 *pn = n;
309                         }
310                 }
311         }
312 }
313
314 static int
315 calc_tcon_pll_fractional(uint64_t fin, uint64_t fout, int *clk_div)
316 {
317         int m;
318
319         /* Test for 1X match */
320         for (m = TCON_PLL_M_MIN; m <= TCON_PLL_M_MAX; m++) {
321                 if (fout == (fin / m)) {
322                         *clk_div = m;
323                         return (CH0_CLK_SRC_SEL_PLL3_1X);
324                 }
325         }
326
327         /* Test for 2X match */
328         for (m = TCON_PLL_M_MIN; m <= TCON_PLL_M_MAX; m++) {
329                 if (fout == ((fin * 2) / m)) {
330                         *clk_div = m;
331                         return (CH0_CLK_SRC_SEL_PLL3_2X);
332                 }
333         }
334
335         return (-1);
336 }
337
338 static int
339 calc_tcon_pll(uint64_t fin, uint64_t fout, uint64_t *pll_freq, int *tcon_pll_div)
340 {
341         uint32_t m, m2, n, n2;
342         uint64_t fsingle, fdouble;
343         int src_sel;
344         bool dbl;
345
346         /* Test fractional freq first */
347         src_sel = calc_tcon_pll_fractional(TCON_PLLREF_FRAC1, fout,
348             tcon_pll_div);
349         if (src_sel != -1) {
350                 *pll_freq = TCON_PLLREF_FRAC1;
351                 return src_sel;
352         }
353         src_sel = calc_tcon_pll_fractional(TCON_PLLREF_FRAC2, fout,
354             tcon_pll_div);
355         if (src_sel != -1) {
356                 *pll_freq = TCON_PLLREF_FRAC2;
357                 return src_sel;
358         }
359
360         m = n = m2 = n2 = 0;
361         dbl = false;
362
363         /* Find the frequency closes to the target dot clock, using
364          * both 1X and 2X PLL inputs as possible candidates.
365          */
366         calc_tcon_pll_integer(TCON_PLLREF, fout, &m, &n);
367         calc_tcon_pll_integer(TCON_PLLREF * 2, fout, &m2, &n2);
368
369         fsingle = m ? (n * TCON_PLLREF) / m : 0;
370         fdouble = m2 ? (n2 * TCON_PLLREF * 2) / m2 : 0;
371
372         if (fdouble > fsingle) {
373                 dbl = true;
374                 m = m2;
375                 n = n2;
376         }
377
378         /* Set desired parent frequency */
379         *pll_freq = n * TCON_PLLREF;
380         *tcon_pll_div = m;
381
382         /* Return the desired source clock */
383         return (dbl ? CH0_CLK_SRC_SEL_PLL3_2X :
384             CH0_CLK_SRC_SEL_PLL3_1X);
385 }
386
387 static int
388 aw_lcdclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
389     int flags, int *stop)
390 {
391         struct aw_lcdclk_softc *sc;
392         struct clknode *parent_clk;
393         const char **parent_names;
394         uint64_t pll_freq;
395         uint32_t val, src_sel;
396         int error, tcon_pll_div;
397
398         sc = clknode_get_softc(clk);
399
400         if (sc->type == AW_LCD_CH0) {
401                 *stop = 0;
402                 return (0);
403         }
404
405         if (sc->id != CLK_IDX_CH1_SCLK2)
406                 return (ENXIO);
407
408         src_sel = calc_tcon_pll(fin, *fout, &pll_freq, &tcon_pll_div);
409
410         parent_names = clknode_get_parent_names(clk);
411         parent_clk = clknode_find_by_name(parent_names[src_sel]);
412
413         if (parent_clk == NULL)
414                 return (ERANGE);
415
416         /* Fetch input frequency */
417         error = clknode_get_freq(parent_clk, &pll_freq);
418         if (error != 0)
419                 return (error);
420
421         *fout = pll_freq / tcon_pll_div;
422         *stop = 1;
423
424         if ((flags & CLK_SET_DRYRUN) != 0)
425                 return (0);
426
427         /* Switch parent clock if necessary */
428         error = clknode_set_parent_by_idx(clk, src_sel);
429         if (error != 0)
430                 return (error);
431
432         error = clknode_set_freq(parent_clk, pll_freq,
433             0, 0);
434         if (error != 0)
435                 return (error);
436
437         /* Fetch new input frequency */
438         error = clknode_get_freq(parent_clk, &pll_freq);
439         if (error != 0)
440                 return (error);
441
442         *fout = pll_freq / tcon_pll_div;
443
444         error = clknode_enable(parent_clk);
445         if (error != 0)
446                 return (error);
447
448         /* Set LCD divisor */
449         DEVICE_LOCK(sc);
450         LCDCLK_READ(sc, &val);
451         val &= ~CH1_CLK_DIV_RATIO_M;
452         val |= ((tcon_pll_div - 1) << CH1_CLK_DIV_RATIO_M_SHIFT);
453         LCDCLK_WRITE(sc, val);
454         DEVICE_UNLOCK(sc);
455
456         return (0);
457 }
458
459 static clknode_method_t aw_lcdclk_clknode_methods[] = {
460         /* Device interface */
461         CLKNODEMETHOD(clknode_init,             aw_lcdclk_init),
462         CLKNODEMETHOD(clknode_set_gate,         aw_lcdclk_set_gate),
463         CLKNODEMETHOD(clknode_set_mux,          aw_lcdclk_set_mux),
464         CLKNODEMETHOD(clknode_recalc_freq,      aw_lcdclk_recalc_freq),
465         CLKNODEMETHOD(clknode_set_freq,         aw_lcdclk_set_freq),
466         CLKNODEMETHOD_END
467 };
468 DEFINE_CLASS_1(aw_lcdclk_clknode, aw_lcdclk_clknode_class,
469     aw_lcdclk_clknode_methods, sizeof(struct aw_lcdclk_softc), clknode_class);
470
471 static int
472 aw_lcdclk_create(device_t dev, struct clkdom *clkdom,
473     const char **parent_names, int parent_cnt, const char *name, int index)
474 {
475         struct aw_lcdclk_softc *sc, *clk_sc;
476         struct clknode_init_def def;
477         struct clknode *clk;
478         phandle_t node;
479
480         sc = device_get_softc(dev);
481         node = ofw_bus_get_node(dev);
482
483         memset(&def, 0, sizeof(def));
484         def.id = index;
485         def.name = name;
486         def.parent_names = parent_names;
487         def.parent_cnt = parent_cnt;
488
489         clk = clknode_create(clkdom, &aw_lcdclk_clknode_class, &def);
490         if (clk == NULL) {
491                 device_printf(dev, "cannot create clknode\n");
492                 return (ENXIO);
493         }
494
495         clk_sc = clknode_get_softc(clk);
496         clk_sc->type = sc->type;
497         clk_sc->reg = sc->reg;
498         clk_sc->clkdev = sc->clkdev;
499         clk_sc->id = index;
500
501         clknode_register(clkdom, clk);
502
503         return (0);
504 }
505
506 static int
507 aw_lcdclk_probe(device_t dev)
508 {
509         enum aw_lcdclk_type type;
510
511         if (!ofw_bus_status_okay(dev))
512                 return (ENXIO);
513
514         type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
515         switch (type) {
516         case AW_LCD_CH0:
517                 device_set_desc(dev, "Allwinner LCD CH0 Clock");
518                 break;
519         case AW_LCD_CH1:
520                 device_set_desc(dev, "Allwinner LCD CH1 Clock");
521                 break;
522         default:
523                 return (ENXIO);
524         }
525
526         return (BUS_PROBE_DEFAULT);
527 }
528
529 static int
530 aw_lcdclk_attach(device_t dev)
531 {
532         struct aw_lcdclk_softc *sc;
533         struct clkdom *clkdom;
534         clk_t clk_parent;
535         bus_size_t psize;
536         phandle_t node;
537         uint32_t *indices;
538         const char **parent_names;
539         const char **names;
540         int error, ncells, nout, i;
541
542         sc = device_get_softc(dev);
543         sc->clkdev = device_get_parent(dev);
544         sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
545
546         node = ofw_bus_get_node(dev);
547
548         if (ofw_reg_to_paddr(node, 0, &sc->reg, &psize, NULL) != 0) {
549                 device_printf(dev, "cannot parse 'reg' property\n");
550                 return (ENXIO);
551         }
552
553         error = ofw_bus_parse_xref_list_get_length(node, "clocks",
554             "#clock-cells", &ncells);
555         if (error != 0) {
556                 device_printf(dev, "cannot get clock count\n");
557                 return (error);
558         }
559
560         parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK);
561         for (i = 0; i < ncells; i++) {
562                 error = clk_get_by_ofw_index(dev, 0, i, &clk_parent);
563                 if (error != 0) {
564                         device_printf(dev, "cannot get clock %d\n", i);
565                         goto fail;
566                 }
567                 parent_names[i] = clk_get_name(clk_parent);
568                 clk_release(clk_parent);
569         }
570
571         nout = clk_parse_ofw_out_names(dev, node, &names, &indices);
572         if (nout == 0) {
573                 device_printf(dev, "no clock outputs found\n");
574                 return (error);
575         }
576
577         clkdom = clkdom_create(dev);
578
579         for (i = 0; i < nout; i++) {
580                 error = aw_lcdclk_create(dev, clkdom, parent_names, ncells,
581                     names[i], nout == 1 ? 1 : i);
582                 if (error)
583                         goto fail;
584         }
585
586         if (clkdom_finit(clkdom) != 0) {
587                 device_printf(dev, "cannot finalize clkdom initialization\n");
588                 error = ENXIO;
589                 goto fail;
590         }
591
592         if (bootverbose)
593                 clkdom_dump(clkdom);
594
595         if (sc->type == AW_LCD_CH0)
596                 hwreset_register_ofw_provider(dev);
597
598         OF_prop_free(parent_names);
599         return (0);
600
601 fail:
602         OF_prop_free(parent_names);
603         return (error);
604 }
605
606 static device_method_t aw_lcdclk_methods[] = {
607         /* Device interface */
608         DEVMETHOD(device_probe,         aw_lcdclk_probe),
609         DEVMETHOD(device_attach,        aw_lcdclk_attach),
610
611         /* Reset interface */
612         DEVMETHOD(hwreset_assert,       aw_lcdclk_hwreset_assert),
613         DEVMETHOD(hwreset_is_asserted,  aw_lcdclk_hwreset_is_asserted),
614
615         DEVMETHOD_END
616 };
617
618 static driver_t aw_lcdclk_driver = {
619         "aw_lcdclk",
620         aw_lcdclk_methods,
621         sizeof(struct aw_lcdclk_softc)
622 };
623
624 static devclass_t aw_lcdclk_devclass;
625
626 EARLY_DRIVER_MODULE(aw_lcdclk, simplebus, aw_lcdclk_driver,
627     aw_lcdclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);