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