1 /* $OpenBSD: aplnco.c,v 1.2 2022/05/29 16:19:08 kettenis Exp $ */
2 /*
3 * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 #include <sys/device.h>
21
22 #include <machine/bus.h>
23 #include <machine/fdt.h>
24
25 #include <dev/ofw/openfirm.h>
26 #include <dev/ofw/ofw_clock.h>
27 #include <dev/ofw/fdt.h>
28
29 #define NCO_LFSR_POLY 0xa01
30 #define NCO_LFSR_MASK 0x7ff
31
32 #define NCO_STRIDE 0x4000
33
34 #define NCO_CTRL(idx) ((idx) * NCO_STRIDE + 0x0000)
35 #define NCO_CTRL_ENABLE (1U << 31)
36 #define NCO_DIV(idx) ((idx) * NCO_STRIDE + 0x0004)
37 #define NCO_DIV_COARSE(div) ((div >> 2) & NCO_LFSR_MASK)
38 #define NCO_DIV_FINE(div) (div & 0x3)
39 #define NCO_INC1(idx) ((idx) * NCO_STRIDE + 0x0008)
40 #define NCO_INC2(idx) ((idx) * NCO_STRIDE + 0x000c)
41
42 #define HREAD4(sc, reg) \
43 (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
44 #define HWRITE4(sc, reg, val) \
45 bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
46 #define HSET4(sc, reg, bits) \
47 HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
48 #define HCLR4(sc, reg, bits) \
49 HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
50
51 struct aplnco_softc {
52 struct device sc_dev;
53 bus_space_tag_t sc_iot;
54 bus_space_handle_t sc_ioh;
55
56 int sc_node;
57 unsigned int sc_nclocks;
58 struct clock_device sc_cd;
59 };
60
61 int aplnco_match(struct device *, void *, void *);
62 void aplnco_attach(struct device *, struct device *, void *);
63
64 const struct cfattach aplnco_ca = {
65 sizeof (struct aplnco_softc), aplnco_match, aplnco_attach
66 };
67
68 struct cfdriver aplnco_cd = {
69 NULL, "aplnco", DV_DULL
70 };
71
72 void aplnco_enable(void *, uint32_t *, int);
73 uint32_t aplnco_get_frequency(void *, uint32_t *);
74 int aplnco_set_frequency(void *, uint32_t *, uint32_t);
75
76 int
aplnco_match(struct device * parent,void * match,void * aux)77 aplnco_match(struct device *parent, void *match, void *aux)
78 {
79 struct fdt_attach_args *faa = aux;
80
81 return OF_is_compatible(faa->fa_node, "apple,nco");
82 }
83
84 void
aplnco_attach(struct device * parent,struct device * self,void * aux)85 aplnco_attach(struct device *parent, struct device *self, void *aux)
86 {
87 struct aplnco_softc *sc = (struct aplnco_softc *)self;
88 struct fdt_attach_args *faa = aux;
89
90 if (faa->fa_nreg < 1) {
91 printf(": no registers\n");
92 return;
93 }
94
95 sc->sc_iot = faa->fa_iot;
96 if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
97 faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
98 printf(": can't map registers\n");
99 return;
100 }
101
102 sc->sc_node = faa->fa_node;
103 sc->sc_nclocks = faa->fa_reg[0].size / NCO_STRIDE;
104
105 printf("\n");
106
107 sc->sc_cd.cd_node = faa->fa_node;
108 sc->sc_cd.cd_cookie = sc;
109 sc->sc_cd.cd_enable = aplnco_enable;
110 sc->sc_cd.cd_get_frequency = aplnco_get_frequency;
111 sc->sc_cd.cd_set_frequency = aplnco_set_frequency;
112 clock_register(&sc->sc_cd);
113 }
114
115 uint16_t
aplnco_lfsr(uint16_t fwd)116 aplnco_lfsr(uint16_t fwd)
117 {
118 uint16_t lfsr = NCO_LFSR_MASK;
119 uint16_t i;
120
121 for (i = NCO_LFSR_MASK; i > 0; i--) {
122 if (lfsr & 1)
123 lfsr = (lfsr >> 1) ^ (NCO_LFSR_POLY >> 1);
124 else
125 lfsr = (lfsr >> 1);
126 if (i == fwd)
127 return lfsr;
128 }
129
130 return 0;
131 }
132
133 uint16_t
aplnco_lfsr_inv(uint16_t inv)134 aplnco_lfsr_inv(uint16_t inv)
135 {
136 uint16_t lfsr = NCO_LFSR_MASK;
137 uint16_t i;
138
139 for (i = NCO_LFSR_MASK; i > 0; i--) {
140 if (lfsr & 1)
141 lfsr = (lfsr >> 1) ^ (NCO_LFSR_POLY >> 1);
142 else
143 lfsr = (lfsr >> 1);
144 if (lfsr == inv)
145 return i;
146 }
147
148 return 0;
149 }
150
151 void
aplnco_enable(void * cookie,uint32_t * cells,int on)152 aplnco_enable(void *cookie, uint32_t *cells, int on)
153 {
154 struct aplnco_softc *sc = cookie;
155 uint32_t idx = cells[0];
156
157 if (idx >= sc->sc_nclocks)
158 return;
159
160 if (on)
161 HSET4(sc, NCO_CTRL(idx), NCO_CTRL_ENABLE);
162 else
163 HCLR4(sc, NCO_CTRL(idx), NCO_CTRL_ENABLE);
164 }
165
166 uint32_t
aplnco_get_frequency(void * cookie,uint32_t * cells)167 aplnco_get_frequency(void *cookie, uint32_t *cells)
168 {
169 struct aplnco_softc *sc = cookie;
170 uint32_t idx = cells[0];
171 uint32_t div, parent_freq;
172 uint16_t coarse, fine;
173 int32_t inc1, inc2;
174 int64_t div64;
175
176 if (idx >= sc->sc_nclocks)
177 return 0;
178
179 parent_freq = clock_get_frequency(sc->sc_node, NULL);
180 div = HREAD4(sc, NCO_DIV(idx));
181 coarse = NCO_DIV_COARSE(div);
182 fine = NCO_DIV_FINE(div);
183
184 coarse = aplnco_lfsr_inv(coarse) + 2;
185 div = (coarse << 2) + fine;
186
187 inc1 = HREAD4(sc, NCO_INC1(idx));
188 inc2 = HREAD4(sc, NCO_INC2(idx));
189 if (inc1 < 0 || inc2 > 0 || (inc1 == 0 && inc2 == 0))
190 return 0;
191
192 div64 = (int64_t)div * (inc1 - inc2) + inc1;
193 if (div64 == 0)
194 return 0;
195
196 return ((int64_t)parent_freq * 2 * (inc1 - inc2)) / div64;
197 }
198
199 int
aplnco_set_frequency(void * cookie,uint32_t * cells,uint32_t freq)200 aplnco_set_frequency(void *cookie, uint32_t *cells, uint32_t freq)
201 {
202 struct aplnco_softc *sc = cookie;
203 uint32_t idx = cells[0];
204 uint32_t div, parent_freq;
205 uint16_t coarse;
206 int32_t inc1, inc2;
207 uint32_t ctrl;
208
209 if (idx >= sc->sc_nclocks)
210 return ENXIO;
211
212 if (freq == 0)
213 return EINVAL;
214
215 parent_freq = clock_get_frequency(sc->sc_node, NULL);
216 div = (parent_freq * 2) / freq;
217 coarse = (div >> 2) - 2;
218 if (coarse > NCO_LFSR_MASK)
219 return EINVAL;
220
221 inc1 = 2 * parent_freq - div * freq;
222 inc2 = -(freq - inc1);
223
224 coarse = aplnco_lfsr(coarse);
225 div = (coarse << 2) + (div & 3);
226
227 ctrl = HREAD4(sc, NCO_CTRL(idx));
228 HWRITE4(sc, NCO_CTRL(idx), ctrl & ~NCO_CTRL_ENABLE);
229 HWRITE4(sc, NCO_DIV(idx), div);
230 HWRITE4(sc, NCO_INC1(idx), inc1);
231 HWRITE4(sc, NCO_INC2(idx), inc2);
232 HWRITE4(sc, NCO_CTRL(idx), ctrl);
233
234 return 0;
235 }
236