xref: /openbsd-src/sys/arch/arm64/dev/aplnco.c (revision c51a0c5ef40d32f90c791642f242384953afebb9)
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