1*c51a0c5eSkettenis /* $OpenBSD: aplnco.c,v 1.2 2022/05/29 16:19:08 kettenis Exp $ */
2725ee01fSkettenis /*
3725ee01fSkettenis * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org>
4725ee01fSkettenis *
5725ee01fSkettenis * Permission to use, copy, modify, and distribute this software for any
6725ee01fSkettenis * purpose with or without fee is hereby granted, provided that the above
7725ee01fSkettenis * copyright notice and this permission notice appear in all copies.
8725ee01fSkettenis *
9725ee01fSkettenis * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10725ee01fSkettenis * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11725ee01fSkettenis * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12725ee01fSkettenis * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13725ee01fSkettenis * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14725ee01fSkettenis * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15725ee01fSkettenis * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16725ee01fSkettenis */
17725ee01fSkettenis
18725ee01fSkettenis #include <sys/param.h>
19725ee01fSkettenis #include <sys/systm.h>
20725ee01fSkettenis #include <sys/device.h>
21725ee01fSkettenis
22725ee01fSkettenis #include <machine/bus.h>
23725ee01fSkettenis #include <machine/fdt.h>
24725ee01fSkettenis
25725ee01fSkettenis #include <dev/ofw/openfirm.h>
26725ee01fSkettenis #include <dev/ofw/ofw_clock.h>
27725ee01fSkettenis #include <dev/ofw/fdt.h>
28725ee01fSkettenis
29725ee01fSkettenis #define NCO_LFSR_POLY 0xa01
30725ee01fSkettenis #define NCO_LFSR_MASK 0x7ff
31725ee01fSkettenis
32725ee01fSkettenis #define NCO_STRIDE 0x4000
33725ee01fSkettenis
34725ee01fSkettenis #define NCO_CTRL(idx) ((idx) * NCO_STRIDE + 0x0000)
35725ee01fSkettenis #define NCO_CTRL_ENABLE (1U << 31)
36725ee01fSkettenis #define NCO_DIV(idx) ((idx) * NCO_STRIDE + 0x0004)
37725ee01fSkettenis #define NCO_DIV_COARSE(div) ((div >> 2) & NCO_LFSR_MASK)
38725ee01fSkettenis #define NCO_DIV_FINE(div) (div & 0x3)
39725ee01fSkettenis #define NCO_INC1(idx) ((idx) * NCO_STRIDE + 0x0008)
40725ee01fSkettenis #define NCO_INC2(idx) ((idx) * NCO_STRIDE + 0x000c)
41725ee01fSkettenis
42725ee01fSkettenis #define HREAD4(sc, reg) \
43725ee01fSkettenis (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
44725ee01fSkettenis #define HWRITE4(sc, reg, val) \
45725ee01fSkettenis bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
46725ee01fSkettenis #define HSET4(sc, reg, bits) \
47725ee01fSkettenis HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
48725ee01fSkettenis #define HCLR4(sc, reg, bits) \
49725ee01fSkettenis HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
50725ee01fSkettenis
51725ee01fSkettenis struct aplnco_softc {
52725ee01fSkettenis struct device sc_dev;
53725ee01fSkettenis bus_space_tag_t sc_iot;
54725ee01fSkettenis bus_space_handle_t sc_ioh;
55725ee01fSkettenis
56725ee01fSkettenis int sc_node;
57725ee01fSkettenis unsigned int sc_nclocks;
58725ee01fSkettenis struct clock_device sc_cd;
59725ee01fSkettenis };
60725ee01fSkettenis
61725ee01fSkettenis int aplnco_match(struct device *, void *, void *);
62725ee01fSkettenis void aplnco_attach(struct device *, struct device *, void *);
63725ee01fSkettenis
64725ee01fSkettenis const struct cfattach aplnco_ca = {
65725ee01fSkettenis sizeof (struct aplnco_softc), aplnco_match, aplnco_attach
66725ee01fSkettenis };
67725ee01fSkettenis
68725ee01fSkettenis struct cfdriver aplnco_cd = {
69725ee01fSkettenis NULL, "aplnco", DV_DULL
70725ee01fSkettenis };
71725ee01fSkettenis
72725ee01fSkettenis void aplnco_enable(void *, uint32_t *, int);
73725ee01fSkettenis uint32_t aplnco_get_frequency(void *, uint32_t *);
74725ee01fSkettenis int aplnco_set_frequency(void *, uint32_t *, uint32_t);
75725ee01fSkettenis
76725ee01fSkettenis int
aplnco_match(struct device * parent,void * match,void * aux)77725ee01fSkettenis aplnco_match(struct device *parent, void *match, void *aux)
78725ee01fSkettenis {
79725ee01fSkettenis struct fdt_attach_args *faa = aux;
80725ee01fSkettenis
81725ee01fSkettenis return OF_is_compatible(faa->fa_node, "apple,nco");
82725ee01fSkettenis }
83725ee01fSkettenis
84725ee01fSkettenis void
aplnco_attach(struct device * parent,struct device * self,void * aux)85725ee01fSkettenis aplnco_attach(struct device *parent, struct device *self, void *aux)
86725ee01fSkettenis {
87725ee01fSkettenis struct aplnco_softc *sc = (struct aplnco_softc *)self;
88725ee01fSkettenis struct fdt_attach_args *faa = aux;
89725ee01fSkettenis
90725ee01fSkettenis if (faa->fa_nreg < 1) {
91725ee01fSkettenis printf(": no registers\n");
92725ee01fSkettenis return;
93725ee01fSkettenis }
94725ee01fSkettenis
95725ee01fSkettenis sc->sc_iot = faa->fa_iot;
96725ee01fSkettenis if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
97725ee01fSkettenis faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
98725ee01fSkettenis printf(": can't map registers\n");
99725ee01fSkettenis return;
100725ee01fSkettenis }
101725ee01fSkettenis
102725ee01fSkettenis sc->sc_node = faa->fa_node;
103725ee01fSkettenis sc->sc_nclocks = faa->fa_reg[0].size / NCO_STRIDE;
104725ee01fSkettenis
105725ee01fSkettenis printf("\n");
106725ee01fSkettenis
107725ee01fSkettenis sc->sc_cd.cd_node = faa->fa_node;
108725ee01fSkettenis sc->sc_cd.cd_cookie = sc;
109725ee01fSkettenis sc->sc_cd.cd_enable = aplnco_enable;
110725ee01fSkettenis sc->sc_cd.cd_get_frequency = aplnco_get_frequency;
111725ee01fSkettenis sc->sc_cd.cd_set_frequency = aplnco_set_frequency;
112725ee01fSkettenis clock_register(&sc->sc_cd);
113725ee01fSkettenis }
114725ee01fSkettenis
115725ee01fSkettenis uint16_t
aplnco_lfsr(uint16_t fwd)116725ee01fSkettenis aplnco_lfsr(uint16_t fwd)
117725ee01fSkettenis {
118725ee01fSkettenis uint16_t lfsr = NCO_LFSR_MASK;
119725ee01fSkettenis uint16_t i;
120725ee01fSkettenis
121725ee01fSkettenis for (i = NCO_LFSR_MASK; i > 0; i--) {
122725ee01fSkettenis if (lfsr & 1)
123725ee01fSkettenis lfsr = (lfsr >> 1) ^ (NCO_LFSR_POLY >> 1);
124725ee01fSkettenis else
125725ee01fSkettenis lfsr = (lfsr >> 1);
126725ee01fSkettenis if (i == fwd)
127725ee01fSkettenis return lfsr;
128725ee01fSkettenis }
129725ee01fSkettenis
130725ee01fSkettenis return 0;
131725ee01fSkettenis }
132725ee01fSkettenis
133725ee01fSkettenis uint16_t
aplnco_lfsr_inv(uint16_t inv)134725ee01fSkettenis aplnco_lfsr_inv(uint16_t inv)
135725ee01fSkettenis {
136725ee01fSkettenis uint16_t lfsr = NCO_LFSR_MASK;
137725ee01fSkettenis uint16_t i;
138725ee01fSkettenis
139725ee01fSkettenis for (i = NCO_LFSR_MASK; i > 0; i--) {
140725ee01fSkettenis if (lfsr & 1)
141725ee01fSkettenis lfsr = (lfsr >> 1) ^ (NCO_LFSR_POLY >> 1);
142725ee01fSkettenis else
143725ee01fSkettenis lfsr = (lfsr >> 1);
144725ee01fSkettenis if (lfsr == inv)
145725ee01fSkettenis return i;
146725ee01fSkettenis }
147725ee01fSkettenis
148725ee01fSkettenis return 0;
149725ee01fSkettenis }
150725ee01fSkettenis
151725ee01fSkettenis void
aplnco_enable(void * cookie,uint32_t * cells,int on)152725ee01fSkettenis aplnco_enable(void *cookie, uint32_t *cells, int on)
153725ee01fSkettenis {
154725ee01fSkettenis struct aplnco_softc *sc = cookie;
155725ee01fSkettenis uint32_t idx = cells[0];
156725ee01fSkettenis
157725ee01fSkettenis if (idx >= sc->sc_nclocks)
158725ee01fSkettenis return;
159725ee01fSkettenis
160725ee01fSkettenis if (on)
161725ee01fSkettenis HSET4(sc, NCO_CTRL(idx), NCO_CTRL_ENABLE);
162725ee01fSkettenis else
163725ee01fSkettenis HCLR4(sc, NCO_CTRL(idx), NCO_CTRL_ENABLE);
164725ee01fSkettenis }
165725ee01fSkettenis
166725ee01fSkettenis uint32_t
aplnco_get_frequency(void * cookie,uint32_t * cells)167725ee01fSkettenis aplnco_get_frequency(void *cookie, uint32_t *cells)
168725ee01fSkettenis {
169725ee01fSkettenis struct aplnco_softc *sc = cookie;
170725ee01fSkettenis uint32_t idx = cells[0];
171725ee01fSkettenis uint32_t div, parent_freq;
172725ee01fSkettenis uint16_t coarse, fine;
173725ee01fSkettenis int32_t inc1, inc2;
174725ee01fSkettenis int64_t div64;
175725ee01fSkettenis
176725ee01fSkettenis if (idx >= sc->sc_nclocks)
177725ee01fSkettenis return 0;
178725ee01fSkettenis
179725ee01fSkettenis parent_freq = clock_get_frequency(sc->sc_node, NULL);
180725ee01fSkettenis div = HREAD4(sc, NCO_DIV(idx));
181725ee01fSkettenis coarse = NCO_DIV_COARSE(div);
182725ee01fSkettenis fine = NCO_DIV_FINE(div);
183725ee01fSkettenis
184725ee01fSkettenis coarse = aplnco_lfsr_inv(coarse) + 2;
185725ee01fSkettenis div = (coarse << 2) + fine;
186725ee01fSkettenis
187725ee01fSkettenis inc1 = HREAD4(sc, NCO_INC1(idx));
188725ee01fSkettenis inc2 = HREAD4(sc, NCO_INC2(idx));
189725ee01fSkettenis if (inc1 < 0 || inc2 > 0 || (inc1 == 0 && inc2 == 0))
190725ee01fSkettenis return 0;
191725ee01fSkettenis
192725ee01fSkettenis div64 = (int64_t)div * (inc1 - inc2) + inc1;
193725ee01fSkettenis if (div64 == 0)
194725ee01fSkettenis return 0;
195725ee01fSkettenis
196725ee01fSkettenis return ((int64_t)parent_freq * 2 * (inc1 - inc2)) / div64;
197725ee01fSkettenis }
198725ee01fSkettenis
199725ee01fSkettenis int
aplnco_set_frequency(void * cookie,uint32_t * cells,uint32_t freq)200725ee01fSkettenis aplnco_set_frequency(void *cookie, uint32_t *cells, uint32_t freq)
201725ee01fSkettenis {
202725ee01fSkettenis struct aplnco_softc *sc = cookie;
203725ee01fSkettenis uint32_t idx = cells[0];
204725ee01fSkettenis uint32_t div, parent_freq;
205725ee01fSkettenis uint16_t coarse;
206725ee01fSkettenis int32_t inc1, inc2;
207725ee01fSkettenis uint32_t ctrl;
208725ee01fSkettenis
209725ee01fSkettenis if (idx >= sc->sc_nclocks)
210725ee01fSkettenis return ENXIO;
211725ee01fSkettenis
212725ee01fSkettenis if (freq == 0)
213725ee01fSkettenis return EINVAL;
214725ee01fSkettenis
215725ee01fSkettenis parent_freq = clock_get_frequency(sc->sc_node, NULL);
216725ee01fSkettenis div = (parent_freq * 2) / freq;
217725ee01fSkettenis coarse = (div >> 2) - 2;
218725ee01fSkettenis if (coarse > NCO_LFSR_MASK)
219725ee01fSkettenis return EINVAL;
220725ee01fSkettenis
221725ee01fSkettenis inc1 = 2 * parent_freq - div * freq;
222725ee01fSkettenis inc2 = -(freq - inc1);
223725ee01fSkettenis
224725ee01fSkettenis coarse = aplnco_lfsr(coarse);
225725ee01fSkettenis div = (coarse << 2) + (div & 3);
226725ee01fSkettenis
227725ee01fSkettenis ctrl = HREAD4(sc, NCO_CTRL(idx));
228725ee01fSkettenis HWRITE4(sc, NCO_CTRL(idx), ctrl & ~NCO_CTRL_ENABLE);
229725ee01fSkettenis HWRITE4(sc, NCO_DIV(idx), div);
230725ee01fSkettenis HWRITE4(sc, NCO_INC1(idx), inc1);
231725ee01fSkettenis HWRITE4(sc, NCO_INC2(idx), inc2);
232725ee01fSkettenis HWRITE4(sc, NCO_CTRL(idx), ctrl);
233725ee01fSkettenis
234725ee01fSkettenis return 0;
235725ee01fSkettenis }
236