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