|
| 1 | +/* $NetBSD $ */ |
| 2 | + |
| 3 | +/*- |
| 4 | +* Copyright (c) 2026 The NetBSD Foundation, Inc. |
| 5 | +* All rights reserved. |
| 6 | +* |
| 7 | +* This code is derived from software contributed to The NetBSD Foundation |
| 8 | +* by Yuri Honegger. |
| 9 | +* |
| 10 | +* Redistribution and use in source and binary forms, with or without |
| 11 | +* modification, are permitted provided that the following conditions |
| 12 | +* are met: |
| 13 | +* 1. Redistributions of source code must retain the above copyright |
| 14 | +* notice, this list of conditions and the following disclaimer. |
| 15 | +* 2. Redistributions in binary form must reproduce the above copyright |
| 16 | +* notice, this list of conditions and the following disclaimer in the |
| 17 | +* documentation and/or other materials provided with the distribution. |
| 18 | +* |
| 19 | +* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
| 20 | +* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| 21 | +* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 22 | +* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
| 23 | +* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 24 | +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 25 | +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 26 | +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 27 | +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 28 | +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 29 | +* POSSIBILITY OF SUCH DAMAGE. |
| 30 | + */ |
| 31 | + |
| 32 | +/* |
| 33 | + * PLL Controller for the TI AM18XX SOC. |
| 34 | + */ |
| 35 | + |
| 36 | +#include <sys/param.h> |
| 37 | +#include <sys/bus.h> |
| 38 | +#include <sys/cdefs.h> |
| 39 | +#include <sys/device.h> |
| 40 | + |
| 41 | +#include <dev/clk/clk_backend.h> |
| 42 | +#include <dev/fdt/fdtvar.h> |
| 43 | + |
| 44 | +#include <arm/fdt/arm_fdtvar.h> |
| 45 | + |
| 46 | +struct am18xx_pllc_softc; |
| 47 | + |
| 48 | +struct am18xx_pllc_clk { |
| 49 | + struct clk clk_base; /* must be first */ |
| 50 | + u_int (*get_rate)(struct am18xx_pllc_softc *, struct am18xx_pllc_clk *); |
| 51 | + u_int clk_index; |
| 52 | +}; |
| 53 | + |
| 54 | +struct am18xx_pllc_config { |
| 55 | + struct am18xx_pllc_clk *sysclks; |
| 56 | + struct am18xx_pllc_clk *auxclk; |
| 57 | + int num_sysclk; |
| 58 | +}; |
| 59 | + |
| 60 | +struct am18xx_pllc_softc { |
| 61 | + bus_space_tag_t sc_bst; |
| 62 | + bus_space_handle_t sc_bsh; |
| 63 | + int sc_sysclk_phandle; |
| 64 | + int sc_auxclk_phandle; |
| 65 | + const struct am18xx_pllc_config *sc_config; |
| 66 | + struct clk_domain sc_clkdom; |
| 67 | + struct clk *sc_ref_clk; |
| 68 | +}; |
| 69 | + |
| 70 | +static int am18xx_pllc_match(device_t, cfdata_t, void *); |
| 71 | +static void am18xx_pllc_attach(device_t, device_t, void *); |
| 72 | +static u_int am18xx_pllc_get_sysclk_rate(struct am18xx_pllc_softc *, |
| 73 | + struct am18xx_pllc_clk *); |
| 74 | +static u_int am18xx_pllc_get_auxclk_rate(struct am18xx_pllc_softc *, |
| 75 | + struct am18xx_pllc_clk *); |
| 76 | +static struct clk * am18xx_pllc_decode(device_t, int, const void *, size_t); |
| 77 | +static struct clk * am18xx_pllc_clk_get(void *, const char *); |
| 78 | +static u_int am18xx_pllc_clk_get_rate(void *, struct clk *); |
| 79 | +static struct clk * am18xx_pllc_clk_get_parent(void *, struct clk *); |
| 80 | + |
| 81 | +CFATTACH_DECL_NEW(am18xxpllc, sizeof(struct am18xx_pllc_softc), |
| 82 | + am18xx_pllc_match, am18xx_pllc_attach, NULL, NULL); |
| 83 | + |
| 84 | +#define AM18XX_PLLC_PLLCTL 0x100 |
| 85 | +#define AM18XX_PLLC_PLLM 0x110 |
| 86 | +#define AM18XX_PLLC_PREDIV 0x114 |
| 87 | +#define AM18XX_PLLC_PLLDIV1 0x118 |
| 88 | +#define AM18XX_PLLC_POSTDIV 0x128 |
| 89 | +#define AM18XX_PLLC_PLLDIV4 0x160 |
| 90 | + |
| 91 | +#define AM18XX_PLLC_PLLCTL_PLLEN __BIT(0) |
| 92 | +#define AM18XX_PLLC_PLLCTL_EXTCLKSRC __BIT(9) |
| 93 | +#define AM18XX_PLLC_PLLM_MULTIPLIER __BITS(4,0) |
| 94 | +#define AM18XX_PLLC_PREDIV_RATIO __BITS(4,0) |
| 95 | +#define AM18XX_PLLC_POSTDIV_RATIO __BITS(4,0) |
| 96 | +#define AM18XX_PLLC_PLLDIV_RATIO __BITS(4,0) |
| 97 | + |
| 98 | +#define PLLC_READ(sc, reg) \ |
| 99 | + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, reg) |
| 100 | +#define PLLC_WRITE(sc, reg, val) \ |
| 101 | + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, reg, val) |
| 102 | + |
| 103 | +#define PLLC_CLK(_i, _name, _rate) \ |
| 104 | + { \ |
| 105 | + .clk_base.name = (_name), \ |
| 106 | + .clk_base.flags = 0, \ |
| 107 | + .get_rate = (_rate), \ |
| 108 | + .clk_index = (_i), \ |
| 109 | + } |
| 110 | + |
| 111 | +static struct am18xx_pllc_clk am18xx_pllc_pll0_auxclk = |
| 112 | + PLLC_CLK(0, "pll0_auxclk", &am18xx_pllc_get_auxclk_rate); |
| 113 | + |
| 114 | +static struct am18xx_pllc_clk am18xx_pllc_pll0_sysclks[] = { |
| 115 | + PLLC_CLK(0, "pll0_sysclk1", &am18xx_pllc_get_sysclk_rate), |
| 116 | + PLLC_CLK(1, "pll0_sysclk2", &am18xx_pllc_get_sysclk_rate), |
| 117 | + PLLC_CLK(2, "pll0_sysclk3", &am18xx_pllc_get_sysclk_rate), |
| 118 | + PLLC_CLK(3, "pll0_sysclk4", &am18xx_pllc_get_sysclk_rate), |
| 119 | + PLLC_CLK(4, "pll0_sysclk5", &am18xx_pllc_get_sysclk_rate), |
| 120 | + PLLC_CLK(5, "pll0_sysclk6", &am18xx_pllc_get_sysclk_rate), |
| 121 | + PLLC_CLK(6, "pll0_sysclk7", &am18xx_pllc_get_sysclk_rate), |
| 122 | +}; |
| 123 | + |
| 124 | +static const struct am18xx_pllc_config am18xx_pllc_pll0_config = { |
| 125 | + .auxclk = &am18xx_pllc_pll0_auxclk, |
| 126 | + .sysclks = am18xx_pllc_pll0_sysclks, |
| 127 | + .num_sysclk = __arraycount(am18xx_pllc_pll0_sysclks), |
| 128 | +}; |
| 129 | + |
| 130 | +static const struct fdtbus_clock_controller_func am18xx_pllc_clk_fdt_funcs = { |
| 131 | + .decode = am18xx_pllc_decode, |
| 132 | +}; |
| 133 | + |
| 134 | +static const struct clk_funcs am18xx_pllc_clk_funcs = { |
| 135 | + .get = am18xx_pllc_clk_get, |
| 136 | + .get_rate = am18xx_pllc_clk_get_rate, |
| 137 | + .get_parent = am18xx_pllc_clk_get_parent, |
| 138 | +}; |
| 139 | + |
| 140 | +static const struct device_compatible_entry compat_data[] = { |
| 141 | + { .compat = "ti,da850-pll0", .data = &am18xx_pllc_pll0_config }, |
| 142 | + DEVICE_COMPAT_EOL |
| 143 | +}; |
| 144 | + |
| 145 | +static struct clk * |
| 146 | +am18xx_pllc_clk_get(void *priv, const char *name) |
| 147 | +{ |
| 148 | + struct am18xx_pllc_softc * const sc = priv; |
| 149 | + |
| 150 | + /* check if it is the auxclk */ |
| 151 | + if (strcmp(sc->sc_config->auxclk->clk_base.name, name) == 0) { |
| 152 | + return &sc->sc_config->auxclk->clk_base; |
| 153 | + } |
| 154 | + |
| 155 | + /* check if it is a sysclk */ |
| 156 | + for (int i = 0; i < sc->sc_config->num_sysclk; i++) { |
| 157 | + if (strcmp(sc->sc_config->sysclks[i].clk_base.name, name) == 0) |
| 158 | + return &sc->sc_config->sysclks[i].clk_base; |
| 159 | + } |
| 160 | + |
| 161 | + return NULL; |
| 162 | +} |
| 163 | + |
| 164 | +static u_int |
| 165 | +am18xx_pllc_clk_get_rate(void *priv, struct clk *clkp) |
| 166 | +{ |
| 167 | + struct am18xx_pllc_softc * const sc = priv; |
| 168 | + struct am18xx_pllc_clk *clk = (struct am18xx_pllc_clk *)clkp; |
| 169 | + |
| 170 | + return clk->get_rate(sc, clk); |
| 171 | +} |
| 172 | + |
| 173 | +static struct clk * |
| 174 | +am18xx_pllc_clk_get_parent(void *priv, struct clk *clkp) |
| 175 | +{ |
| 176 | + struct am18xx_pllc_softc * const sc = priv; |
| 177 | + |
| 178 | + return sc->sc_ref_clk; |
| 179 | +} |
| 180 | + |
| 181 | +static u_int |
| 182 | +am18xx_pllc_get_sysclk_rate(struct am18xx_pllc_softc *sc, |
| 183 | + struct am18xx_pllc_clk *clk) |
| 184 | +{ |
| 185 | + uint32_t pllctl_reg = PLLC_READ(sc, AM18XX_PLLC_PLLCTL); |
| 186 | + |
| 187 | + uint32_t prediv_reg = PLLC_READ(sc, AM18XX_PLLC_PREDIV); |
| 188 | + uint32_t prediv_ratio = (prediv_reg & AM18XX_PLLC_PREDIV_RATIO) + 1; |
| 189 | + |
| 190 | + uint32_t pllm_reg = PLLC_READ(sc, AM18XX_PLLC_PLLM); |
| 191 | + uint32_t pllm_multiplier = (pllm_reg & AM18XX_PLLC_PLLM_MULTIPLIER) + 1; |
| 192 | + |
| 193 | + uint32_t postdiv_reg = PLLC_READ(sc, AM18XX_PLLC_POSTDIV); |
| 194 | + uint32_t postdiv_ratio = (postdiv_reg & AM18XX_PLLC_POSTDIV_RATIO) + 1; |
| 195 | + |
| 196 | + uint32_t plldiv_regaddr; |
| 197 | + if (clk->clk_index <= 2) { |
| 198 | + plldiv_regaddr = AM18XX_PLLC_PLLDIV1 + 4 * clk->clk_index; |
| 199 | + } else { |
| 200 | + plldiv_regaddr = AM18XX_PLLC_PLLDIV4 + 4 * (clk->clk_index - 3); |
| 201 | + } |
| 202 | + uint32_t plldiv_reg = PLLC_READ(sc, plldiv_regaddr); |
| 203 | + uint32_t plldiv_ratio = (plldiv_reg & AM18XX_PLLC_PLLDIV_RATIO) + 1; |
| 204 | + |
| 205 | + u_int ref_clk_rate = clk_get_rate(sc->sc_ref_clk); |
| 206 | + |
| 207 | + if (pllctl_reg & AM18XX_PLLC_PLLCTL_PLLEN) { |
| 208 | + /* PLL enabled */ |
| 209 | + ref_clk_rate /= prediv_ratio; |
| 210 | + ref_clk_rate *= pllm_multiplier; |
| 211 | + ref_clk_rate /= postdiv_ratio; |
| 212 | + } else { |
| 213 | + /* bypass mode (ensure we aren't using the other PLL)*/ |
| 214 | + KASSERT((pllctl_reg & AM18XX_PLLC_PLLCTL_EXTCLKSRC) == 0); |
| 215 | + } |
| 216 | + |
| 217 | + ref_clk_rate /= plldiv_ratio; |
| 218 | + |
| 219 | + return ref_clk_rate; |
| 220 | +} |
| 221 | + |
| 222 | +static u_int |
| 223 | +am18xx_pllc_get_auxclk_rate(struct am18xx_pllc_softc *sc, |
| 224 | + struct am18xx_pllc_clk *clk) |
| 225 | +{ |
| 226 | + return clk_get_rate(sc->sc_ref_clk); |
| 227 | +} |
| 228 | + |
| 229 | +static struct clk * |
| 230 | +am18xx_pllc_decode(device_t dev, int cc_phandle, const void *data, size_t len) |
| 231 | +{ |
| 232 | + struct am18xx_pllc_softc * const sc = device_private(dev); |
| 233 | + const u_int *cells = data; |
| 234 | + |
| 235 | + if (cc_phandle == sc->sc_sysclk_phandle) { |
| 236 | + if (len != 4) |
| 237 | + return NULL; |
| 238 | + const u_int clock_index = be32toh(cells[0]) - 1; |
| 239 | + if (clock_index >= sc->sc_config->num_sysclk) { |
| 240 | + return NULL; |
| 241 | + } |
| 242 | + |
| 243 | + return &sc->sc_config->sysclks[clock_index].clk_base; |
| 244 | + } else if (cc_phandle == sc->sc_auxclk_phandle) { |
| 245 | + return &sc->sc_config->auxclk->clk_base; |
| 246 | + } |
| 247 | + |
| 248 | + return NULL; |
| 249 | +} |
| 250 | + |
| 251 | +int |
| 252 | +am18xx_pllc_match(device_t parent, cfdata_t cf, void *aux) |
| 253 | +{ |
| 254 | + struct fdt_attach_args * const faa = aux; |
| 255 | + |
| 256 | + return of_compatible_match(faa->faa_phandle, compat_data); |
| 257 | +} |
| 258 | + |
| 259 | +void |
| 260 | +am18xx_pllc_attach(device_t parent, device_t self, void *aux) |
| 261 | +{ |
| 262 | + struct am18xx_pllc_softc * const sc = device_private(self); |
| 263 | + struct fdt_attach_args * const faa = aux; |
| 264 | + const int phandle = faa->faa_phandle; |
| 265 | + bus_addr_t addr; |
| 266 | + bus_size_t size; |
| 267 | + |
| 268 | + sc->sc_bst = faa->faa_bst; |
| 269 | + sc->sc_config = of_compatible_lookup(phandle, compat_data)->data; |
| 270 | + |
| 271 | + /* map PSC control registers */ |
| 272 | + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { |
| 273 | + aprint_error(": couldn't get registers\n"); |
| 274 | + return; |
| 275 | + } |
| 276 | + if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { |
| 277 | + aprint_error(": couldn't map registers\n"); |
| 278 | + return; |
| 279 | + } |
| 280 | + |
| 281 | + /* get parent clock */ |
| 282 | + sc->sc_ref_clk = fdtbus_clock_get_index(phandle, 0); |
| 283 | + if (sc->sc_ref_clk == NULL) { |
| 284 | + aprint_error(": couldn't get reference clock\n"); |
| 285 | + return; |
| 286 | + } |
| 287 | + |
| 288 | + /* initialize clock domain */ |
| 289 | + sc->sc_clkdom.name = device_xname(self); |
| 290 | + sc->sc_clkdom.funcs = &am18xx_pllc_clk_funcs; |
| 291 | + sc->sc_clkdom.priv = sc; |
| 292 | + |
| 293 | + /* initialize the clocks */ |
| 294 | + sc->sc_config->auxclk->clk_base.domain = &sc->sc_clkdom; |
| 295 | + clk_attach(&sc->sc_config->auxclk->clk_base); |
| 296 | + for (int i = 0; i < sc->sc_config->num_sysclk; i++) { |
| 297 | + sc->sc_config->sysclks[i].clk_base.domain = &sc->sc_clkdom; |
| 298 | + clk_attach(&sc->sc_config->sysclks[i].clk_base); |
| 299 | + } |
| 300 | + |
| 301 | + /* register auxclk fdt controller*/ |
| 302 | + sc->sc_auxclk_phandle = of_find_firstchild_byname(phandle, "auxclk"); |
| 303 | + if (sc->sc_auxclk_phandle < 0) { |
| 304 | + aprint_error(": couldn't get pll0_auxclk child\n"); |
| 305 | + return; |
| 306 | + } |
| 307 | + fdtbus_register_clock_controller(self, sc->sc_auxclk_phandle, |
| 308 | + &am18xx_pllc_clk_fdt_funcs); |
| 309 | + |
| 310 | + /* register sysclk fdt controller*/ |
| 311 | + sc->sc_sysclk_phandle = of_find_firstchild_byname(phandle, "sysclk"); |
| 312 | + if (sc->sc_sysclk_phandle < 0) { |
| 313 | + aprint_error(": couldn't get pll0_sysclk child\n"); |
| 314 | + return; |
| 315 | + } |
| 316 | + fdtbus_register_clock_controller(self, sc->sc_sysclk_phandle, |
| 317 | + &am18xx_pllc_clk_fdt_funcs); |
| 318 | + |
| 319 | + aprint_normal("\n"); |
| 320 | +} |
| 321 | + |
0 commit comments