| 1 | /* |
| 2 | * Nuvoton NPCM7xx/8xx Clock Control Registers. |
| 3 | * |
| 4 | * Copyright 2020 Google LLC |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify it |
| 7 | * under the terms of the GNU General Public License as published by the |
| 8 | * Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * for more details. |
| 15 | */ |
| 16 | |
| 17 | #include "qemu/osdep.h" |
| 18 | |
| 19 | #include "hw/misc/npcm_clk.h" |
| 20 | #include "hw/timer/npcm7xx_timer.h" |
| 21 | #include "hw/core/qdev-clock.h" |
| 22 | #include "migration/vmstate.h" |
| 23 | #include "qemu/error-report.h" |
| 24 | #include "qemu/log.h" |
| 25 | #include "qemu/module.h" |
| 26 | #include "qemu/timer.h" |
| 27 | #include "qemu/units.h" |
| 28 | #include "trace.h" |
| 29 | #include "system/watchdog.h" |
| 30 | |
| 31 | /* |
| 32 | * The reference clock hz, and the SECCNT and CNTR25M registers in this module, |
| 33 | * is always 25 MHz. |
| 34 | */ |
| 35 | #define NPCM7XX_CLOCK_REF_HZ (25000000) |
| 36 | |
| 37 | /* Register Field Definitions */ |
| 38 | #define NPCM7XX_CLK_WDRCR_CA9C BIT(0) /* Cortex-A9 Cores */ |
| 39 | |
| 40 | #define PLLCON_LOKI BIT(31) |
| 41 | #define PLLCON_LOKS BIT(30) |
| 42 | #define PLLCON_PWDEN BIT(12) |
| 43 | #define PLLCON_FBDV(con) extract32((con), 16, 12) |
| 44 | #define PLLCON_OTDV2(con) extract32((con), 13, 3) |
| 45 | #define PLLCON_OTDV1(con) extract32((con), 8, 3) |
| 46 | #define PLLCON_INDV(con) extract32((con), 0, 6) |
| 47 | |
| 48 | enum NPCM7xxCLKRegisters { |
| 49 | NPCM7XX_CLK_CLKEN1, |
| 50 | NPCM7XX_CLK_CLKSEL, |
| 51 | NPCM7XX_CLK_CLKDIV1, |
| 52 | NPCM7XX_CLK_PLLCON0, |
| 53 | NPCM7XX_CLK_PLLCON1, |
| 54 | NPCM7XX_CLK_SWRSTR, |
| 55 | NPCM7XX_CLK_IPSRST1 = 0x20 / sizeof(uint32_t), |
| 56 | NPCM7XX_CLK_IPSRST2, |
| 57 | NPCM7XX_CLK_CLKEN2, |
| 58 | NPCM7XX_CLK_CLKDIV2, |
| 59 | NPCM7XX_CLK_CLKEN3, |
| 60 | NPCM7XX_CLK_IPSRST3, |
| 61 | NPCM7XX_CLK_WD0RCR, |
| 62 | NPCM7XX_CLK_WD1RCR, |
| 63 | NPCM7XX_CLK_WD2RCR, |
| 64 | NPCM7XX_CLK_SWRSTC1, |
| 65 | NPCM7XX_CLK_SWRSTC2, |
| 66 | NPCM7XX_CLK_SWRSTC3, |
| 67 | NPCM7XX_CLK_SWRSTC4, |
| 68 | NPCM7XX_CLK_PLLCON2, |
| 69 | NPCM7XX_CLK_CLKDIV3, |
| 70 | NPCM7XX_CLK_CORSTC, |
| 71 | NPCM7XX_CLK_PLLCONG, |
| 72 | NPCM7XX_CLK_AHBCKFI, |
| 73 | NPCM7XX_CLK_SECCNT, |
| 74 | NPCM7XX_CLK_CNTR25M, |
| 75 | }; |
| 76 | |
| 77 | enum NPCM8xxCLKRegisters { |
| 78 | NPCM8XX_CLK_CLKEN1, |
| 79 | NPCM8XX_CLK_CLKSEL, |
| 80 | NPCM8XX_CLK_CLKDIV1, |
| 81 | NPCM8XX_CLK_PLLCON0, |
| 82 | NPCM8XX_CLK_PLLCON1, |
| 83 | NPCM8XX_CLK_SWRSTR, |
| 84 | NPCM8XX_CLK_IPSRST1 = 0x20 / sizeof(uint32_t), |
| 85 | NPCM8XX_CLK_IPSRST2, |
| 86 | NPCM8XX_CLK_CLKEN2, |
| 87 | NPCM8XX_CLK_CLKDIV2, |
| 88 | NPCM8XX_CLK_CLKEN3, |
| 89 | NPCM8XX_CLK_IPSRST3, |
| 90 | NPCM8XX_CLK_WD0RCR, |
| 91 | NPCM8XX_CLK_WD1RCR, |
| 92 | NPCM8XX_CLK_WD2RCR, |
| 93 | NPCM8XX_CLK_SWRSTC1, |
| 94 | NPCM8XX_CLK_SWRSTC2, |
| 95 | NPCM8XX_CLK_SWRSTC3, |
| 96 | NPCM8XX_CLK_TIPRSTC, |
| 97 | NPCM8XX_CLK_PLLCON2, |
| 98 | NPCM8XX_CLK_CLKDIV3, |
| 99 | NPCM8XX_CLK_CORSTC, |
| 100 | NPCM8XX_CLK_PLLCONG, |
| 101 | NPCM8XX_CLK_AHBCKFI, |
| 102 | NPCM8XX_CLK_SECCNT, |
| 103 | NPCM8XX_CLK_CNTR25M, |
| 104 | /* Registers unique to NPCM8XX SoC */ |
| 105 | NPCM8XX_CLK_CLKEN4, |
| 106 | NPCM8XX_CLK_IPSRST4, |
| 107 | NPCM8XX_CLK_BUSTO, |
| 108 | NPCM8XX_CLK_CLKDIV4, |
| 109 | NPCM8XX_CLK_WD0RCRB, |
| 110 | NPCM8XX_CLK_WD1RCRB, |
| 111 | NPCM8XX_CLK_WD2RCRB, |
| 112 | NPCM8XX_CLK_SWRSTC1B, |
| 113 | NPCM8XX_CLK_SWRSTC2B, |
| 114 | NPCM8XX_CLK_SWRSTC3B, |
| 115 | NPCM8XX_CLK_TIPRSTCB, |
| 116 | NPCM8XX_CLK_CORSTCB, |
| 117 | NPCM8XX_CLK_IPSRSTDIS1, |
| 118 | NPCM8XX_CLK_IPSRSTDIS2, |
| 119 | NPCM8XX_CLK_IPSRSTDIS3, |
| 120 | NPCM8XX_CLK_IPSRSTDIS4, |
| 121 | NPCM8XX_CLK_CLKENDIS1, |
| 122 | NPCM8XX_CLK_CLKENDIS2, |
| 123 | NPCM8XX_CLK_CLKENDIS3, |
| 124 | NPCM8XX_CLK_CLKENDIS4, |
| 125 | NPCM8XX_CLK_THRTL_CNT, |
| 126 | }; |
| 127 | |
| 128 | /* |
| 129 | * These reset values were taken from version 0.91 of the NPCM750R data sheet. |
| 130 | * |
| 131 | * All are loaded on power-up reset. CLKENx and SWRSTR should also be loaded on |
| 132 | * core domain reset, but this reset type is not yet supported by QEMU. |
| 133 | */ |
| 134 | static const uint32_t npcm7xx_cold_reset_values[NPCM7XX_CLK_NR_REGS] = { |
| 135 | [NPCM7XX_CLK_CLKEN1] = 0xffffffff, |
| 136 | [NPCM7XX_CLK_CLKSEL] = 0x004aaaaa, |
| 137 | [NPCM7XX_CLK_CLKDIV1] = 0x5413f855, |
| 138 | [NPCM7XX_CLK_PLLCON0] = 0x00222101 | PLLCON_LOKI, |
| 139 | [NPCM7XX_CLK_PLLCON1] = 0x00202101 | PLLCON_LOKI, |
| 140 | [NPCM7XX_CLK_IPSRST1] = 0x00001000, |
| 141 | [NPCM7XX_CLK_IPSRST2] = 0x80000000, |
| 142 | [NPCM7XX_CLK_CLKEN2] = 0xffffffff, |
| 143 | [NPCM7XX_CLK_CLKDIV2] = 0xaa4f8f9f, |
| 144 | [NPCM7XX_CLK_CLKEN3] = 0xffffffff, |
| 145 | [NPCM7XX_CLK_IPSRST3] = 0x03000000, |
| 146 | [NPCM7XX_CLK_WD0RCR] = 0xffffffff, |
| 147 | [NPCM7XX_CLK_WD1RCR] = 0xffffffff, |
| 148 | [NPCM7XX_CLK_WD2RCR] = 0xffffffff, |
| 149 | [NPCM7XX_CLK_SWRSTC1] = 0x00000003, |
| 150 | [NPCM7XX_CLK_PLLCON2] = 0x00c02105 | PLLCON_LOKI, |
| 151 | [NPCM7XX_CLK_CORSTC] = 0x04000003, |
| 152 | [NPCM7XX_CLK_PLLCONG] = 0x01228606 | PLLCON_LOKI, |
| 153 | [NPCM7XX_CLK_AHBCKFI] = 0x000000c8, |
| 154 | }; |
| 155 | |
| 156 | /* |
| 157 | * These reset values were taken from version 0.92 of the NPCM8xx data sheet. |
| 158 | */ |
| 159 | static const uint32_t npcm8xx_cold_reset_values[NPCM8XX_CLK_NR_REGS] = { |
| 160 | [NPCM8XX_CLK_CLKEN1] = 0xffffffff, |
| 161 | [NPCM8XX_CLK_CLKSEL] = 0x154aaaaa, |
| 162 | [NPCM8XX_CLK_CLKDIV1] = 0x5413f855, |
| 163 | [NPCM8XX_CLK_PLLCON0] = 0x00222101 | PLLCON_LOKI, |
| 164 | [NPCM8XX_CLK_PLLCON1] = 0x00202101 | PLLCON_LOKI, |
| 165 | [NPCM8XX_CLK_IPSRST1] = 0x00001000, |
| 166 | [NPCM8XX_CLK_IPSRST2] = 0x80000000, |
| 167 | [NPCM8XX_CLK_CLKEN2] = 0xffffffff, |
| 168 | [NPCM8XX_CLK_CLKDIV2] = 0xaa4f8f9f, |
| 169 | [NPCM8XX_CLK_CLKEN3] = 0xffffffff, |
| 170 | [NPCM8XX_CLK_IPSRST3] = 0x03000000, |
| 171 | [NPCM8XX_CLK_WD0RCR] = 0xffffffff, |
| 172 | [NPCM8XX_CLK_WD1RCR] = 0xffffffff, |
| 173 | [NPCM8XX_CLK_WD2RCR] = 0xffffffff, |
| 174 | [NPCM8XX_CLK_SWRSTC1] = 0x00000003, |
| 175 | [NPCM8XX_CLK_SWRSTC2] = 0x00000001, |
| 176 | [NPCM8XX_CLK_SWRSTC3] = 0x00000001, |
| 177 | [NPCM8XX_CLK_TIPRSTC] = 0x00000001, |
| 178 | [NPCM8XX_CLK_PLLCON2] = 0x00c02105 | PLLCON_LOKI, |
| 179 | [NPCM8XX_CLK_CLKDIV3] = 0x00009100, |
| 180 | [NPCM8XX_CLK_CORSTC] = 0x04000003, |
| 181 | [NPCM8XX_CLK_PLLCONG] = 0x01228606 | PLLCON_LOKI, |
| 182 | [NPCM8XX_CLK_AHBCKFI] = 0x000000c8, |
| 183 | [NPCM8XX_CLK_CLKEN4] = 0xffffffff, |
| 184 | [NPCM8XX_CLK_CLKDIV4] = 0x70009000, |
| 185 | [NPCM8XX_CLK_IPSRST4] = 0x02000000, |
| 186 | [NPCM8XX_CLK_WD0RCRB] = 0xfffffe71, |
| 187 | [NPCM8XX_CLK_WD1RCRB] = 0xfffffe71, |
| 188 | [NPCM8XX_CLK_WD2RCRB] = 0xfffffe71, |
| 189 | [NPCM8XX_CLK_SWRSTC1B] = 0xfffffe71, |
| 190 | [NPCM8XX_CLK_SWRSTC2B] = 0xfffffe71, |
| 191 | [NPCM8XX_CLK_SWRSTC3B] = 0xfffffe71, |
| 192 | [NPCM8XX_CLK_TIPRSTCB] = 0xfffffe71, |
| 193 | [NPCM8XX_CLK_CORSTCB] = 0xfffffe71, |
| 194 | }; |
| 195 | |
| 196 | /* The number of watchdogs that can trigger a reset. */ |
| 197 | #define NPCM7XX_NR_WATCHDOGS (3) |
| 198 | |
| 199 | /* Clock converter functions */ |
| 200 | |
| 201 | #define TYPE_NPCM7XX_CLOCK_PLL "npcm7xx-clock-pll" |
| 202 | #define NPCM7XX_CLOCK_PLL(obj) OBJECT_CHECK(NPCM7xxClockPLLState, \ |
| 203 | (obj), TYPE_NPCM7XX_CLOCK_PLL) |
| 204 | #define TYPE_NPCM7XX_CLOCK_SEL "npcm7xx-clock-sel" |
| 205 | #define NPCM7XX_CLOCK_SEL(obj) OBJECT_CHECK(NPCM7xxClockSELState, \ |
| 206 | (obj), TYPE_NPCM7XX_CLOCK_SEL) |
| 207 | #define TYPE_NPCM7XX_CLOCK_DIVIDER "npcm7xx-clock-divider" |
| 208 | #define NPCM7XX_CLOCK_DIVIDER(obj) OBJECT_CHECK(NPCM7xxClockDividerState, \ |
| 209 | (obj), TYPE_NPCM7XX_CLOCK_DIVIDER) |
| 210 | |
| 211 | static void npcm7xx_clk_update_pll(void *opaque) |
| 212 | { |
| 213 | NPCM7xxClockPLLState *s = opaque; |
| 214 | uint32_t con = s->clk->regs[s->reg]; |
| 215 | uint64_t freq, freq_div; |
| 216 | |
| 217 | /* The PLL is grounded if it is not locked yet. */ |
| 218 | if (con & PLLCON_LOKI) { |
| 219 | freq = clock_get_hz(s->clock_in); |
| 220 | freq *= PLLCON_FBDV(con); |
| 221 | freq_div = PLLCON_INDV(con) * PLLCON_OTDV1(con) * PLLCON_OTDV2(con); |
| 222 | freq = freq_div ? freq / freq_div : 0; |
| 223 | } else { |
| 224 | freq = 0; |
| 225 | } |
| 226 | |
| 227 | clock_update_hz(s->clock_out, freq); |
| 228 | } |
| 229 | |
| 230 | static void npcm7xx_clk_update_sel(void *opaque) |
| 231 | { |
| 232 | NPCM7xxClockSELState *s = opaque; |
| 233 | uint32_t index = extract32(s->clk->regs[NPCM7XX_CLK_CLKSEL], s->offset, |
| 234 | s->len); |
| 235 | |
| 236 | if (index >= s->input_size) { |
| 237 | qemu_log_mask(LOG_GUEST_ERROR, |
| 238 | "%s: SEL index: %u out of range\n", |
| 239 | __func__, index); |
| 240 | index = 0; |
| 241 | } |
| 242 | clock_update_hz(s->clock_out, clock_get_hz(s->clock_in[index])); |
| 243 | } |
| 244 | |
| 245 | static void npcm7xx_clk_update_divider(void *opaque) |
| 246 | { |
| 247 | NPCM7xxClockDividerState *s = opaque; |
| 248 | uint32_t freq; |
| 249 | |
| 250 | freq = s->divide(s); |
| 251 | clock_update_hz(s->clock_out, freq); |
| 252 | } |
| 253 | |
| 254 | static uint32_t divide_by_constant(NPCM7xxClockDividerState *s) |
| 255 | { |
| 256 | return clock_get_hz(s->clock_in) / s->divisor; |
| 257 | } |
| 258 | |
| 259 | static uint32_t divide_by_reg_divisor(NPCM7xxClockDividerState *s) |
| 260 | { |
| 261 | return clock_get_hz(s->clock_in) / |
| 262 | (extract32(s->clk->regs[s->reg], s->offset, s->len) + 1); |
| 263 | } |
| 264 | |
| 265 | static uint32_t divide_by_reg_divisor_times_2(NPCM7xxClockDividerState *s) |
| 266 | { |
| 267 | return divide_by_reg_divisor(s) / 2; |
| 268 | } |
| 269 | |
| 270 | static uint32_t shift_by_reg_divisor(NPCM7xxClockDividerState *s) |
| 271 | { |
| 272 | return clock_get_hz(s->clock_in) >> |
| 273 | extract32(s->clk->regs[s->reg], s->offset, s->len); |
| 274 | } |
| 275 | |
| 276 | static NPCM7xxClockPLL find_pll_by_reg(enum NPCM7xxCLKRegisters reg) |
| 277 | { |
| 278 | switch (reg) { |
| 279 | case NPCM7XX_CLK_PLLCON0: |
| 280 | return NPCM7XX_CLOCK_PLL0; |
| 281 | case NPCM7XX_CLK_PLLCON1: |
| 282 | return NPCM7XX_CLOCK_PLL1; |
| 283 | case NPCM7XX_CLK_PLLCON2: |
| 284 | return NPCM7XX_CLOCK_PLL2; |
| 285 | case NPCM7XX_CLK_PLLCONG: |
| 286 | return NPCM7XX_CLOCK_PLLG; |
| 287 | default: |
| 288 | g_assert_not_reached(); |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | static void npcm7xx_clk_update_all_plls(NPCMCLKState *clk) |
| 293 | { |
| 294 | int i; |
| 295 | |
| 296 | for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { |
| 297 | npcm7xx_clk_update_pll(&clk->plls[i]); |
| 298 | } |
| 299 | } |
| 300 | |
| 301 | static void npcm7xx_clk_update_all_sels(NPCMCLKState *clk) |
| 302 | { |
| 303 | int i; |
| 304 | |
| 305 | for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { |
| 306 | npcm7xx_clk_update_sel(&clk->sels[i]); |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | static void npcm7xx_clk_update_all_dividers(NPCMCLKState *clk) |
| 311 | { |
| 312 | int i; |
| 313 | |
| 314 | for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { |
| 315 | npcm7xx_clk_update_divider(&clk->dividers[i]); |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | static void npcm7xx_clk_update_all_clocks(NPCMCLKState *clk) |
| 320 | { |
| 321 | clock_update_hz(clk->clkref, NPCM7XX_CLOCK_REF_HZ); |
| 322 | npcm7xx_clk_update_all_plls(clk); |
| 323 | npcm7xx_clk_update_all_sels(clk); |
| 324 | npcm7xx_clk_update_all_dividers(clk); |
| 325 | } |
| 326 | |
| 327 | /* Types of clock sources. */ |
| 328 | typedef enum ClockSrcType { |
| 329 | CLKSRC_REF, |
| 330 | CLKSRC_PLL, |
| 331 | CLKSRC_SEL, |
| 332 | CLKSRC_DIV, |
| 333 | } ClockSrcType; |
| 334 | |
| 335 | typedef struct PLLInitInfo { |
| 336 | const char *name; |
| 337 | ClockSrcType src_type; |
| 338 | int src_index; |
| 339 | int reg; |
| 340 | const char *public_name; |
| 341 | } PLLInitInfo; |
| 342 | |
| 343 | typedef struct SELInitInfo { |
| 344 | const char *name; |
| 345 | uint8_t input_size; |
| 346 | ClockSrcType src_type[NPCM7XX_CLK_SEL_MAX_INPUT]; |
| 347 | int src_index[NPCM7XX_CLK_SEL_MAX_INPUT]; |
| 348 | int offset; |
| 349 | int len; |
| 350 | const char *public_name; |
| 351 | } SELInitInfo; |
| 352 | |
| 353 | typedef struct DividerInitInfo { |
| 354 | const char *name; |
| 355 | ClockSrcType src_type; |
| 356 | int src_index; |
| 357 | uint32_t (*divide)(NPCM7xxClockDividerState *s); |
| 358 | int reg; /* not used when type == CONSTANT */ |
| 359 | int offset; /* not used when type == CONSTANT */ |
| 360 | int len; /* not used when type == CONSTANT */ |
| 361 | int divisor; /* used only when type == CONSTANT */ |
| 362 | const char *public_name; |
| 363 | } DividerInitInfo; |
| 364 | |
| 365 | static const PLLInitInfo pll_init_info_list[] = { |
| 366 | [NPCM7XX_CLOCK_PLL0] = { |
| 367 | .name = "pll0", |
| 368 | .src_type = CLKSRC_REF, |
| 369 | .reg = NPCM7XX_CLK_PLLCON0, |
| 370 | }, |
| 371 | [NPCM7XX_CLOCK_PLL1] = { |
| 372 | .name = "pll1", |
| 373 | .src_type = CLKSRC_REF, |
| 374 | .reg = NPCM7XX_CLK_PLLCON1, |
| 375 | }, |
| 376 | [NPCM7XX_CLOCK_PLL2] = { |
| 377 | .name = "pll2", |
| 378 | .src_type = CLKSRC_REF, |
| 379 | .reg = NPCM7XX_CLK_PLLCON2, |
| 380 | }, |
| 381 | [NPCM7XX_CLOCK_PLLG] = { |
| 382 | .name = "pllg", |
| 383 | .src_type = CLKSRC_REF, |
| 384 | .reg = NPCM7XX_CLK_PLLCONG, |
| 385 | }, |
| 386 | }; |
| 387 | |
| 388 | static const SELInitInfo sel_init_info_list[] = { |
| 389 | [NPCM7XX_CLOCK_PIXCKSEL] = { |
| 390 | .name = "pixcksel", |
| 391 | .input_size = 2, |
| 392 | .src_type = {CLKSRC_PLL, CLKSRC_REF}, |
| 393 | .src_index = {NPCM7XX_CLOCK_PLLG, 0}, |
| 394 | .offset = 5, |
| 395 | .len = 1, |
| 396 | .public_name = "pixel-clock", |
| 397 | }, |
| 398 | [NPCM7XX_CLOCK_MCCKSEL] = { |
| 399 | .name = "mccksel", |
| 400 | .input_size = 4, |
| 401 | .src_type = {CLKSRC_DIV, CLKSRC_REF, CLKSRC_REF, |
| 402 | /*MCBPCK, shouldn't be used in normal operation*/ |
| 403 | CLKSRC_REF}, |
| 404 | .src_index = {NPCM7XX_CLOCK_PLL1D2, 0, 0, 0}, |
| 405 | .offset = 12, |
| 406 | .len = 2, |
| 407 | .public_name = "mc-phy-clock", |
| 408 | }, |
| 409 | [NPCM7XX_CLOCK_CPUCKSEL] = { |
| 410 | .name = "cpucksel", |
| 411 | .input_size = 4, |
| 412 | .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, |
| 413 | /*SYSBPCK, shouldn't be used in normal operation*/ |
| 414 | CLKSRC_REF}, |
| 415 | .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, 0}, |
| 416 | .offset = 0, |
| 417 | .len = 2, |
| 418 | .public_name = "system-clock", |
| 419 | }, |
| 420 | [NPCM7XX_CLOCK_CLKOUTSEL] = { |
| 421 | .name = "clkoutsel", |
| 422 | .input_size = 5, |
| 423 | .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, |
| 424 | CLKSRC_PLL, CLKSRC_DIV}, |
| 425 | .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, |
| 426 | NPCM7XX_CLOCK_PLLG, NPCM7XX_CLOCK_PLL2D2}, |
| 427 | .offset = 18, |
| 428 | .len = 3, |
| 429 | .public_name = "tock", |
| 430 | }, |
| 431 | [NPCM7XX_CLOCK_UARTCKSEL] = { |
| 432 | .name = "uartcksel", |
| 433 | .input_size = 4, |
| 434 | .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, |
| 435 | .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, |
| 436 | NPCM7XX_CLOCK_PLL2D2}, |
| 437 | .offset = 8, |
| 438 | .len = 2, |
| 439 | }, |
| 440 | [NPCM7XX_CLOCK_TIMCKSEL] = { |
| 441 | .name = "timcksel", |
| 442 | .input_size = 4, |
| 443 | .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, |
| 444 | .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, |
| 445 | NPCM7XX_CLOCK_PLL2D2}, |
| 446 | .offset = 14, |
| 447 | .len = 2, |
| 448 | }, |
| 449 | [NPCM7XX_CLOCK_SDCKSEL] = { |
| 450 | .name = "sdcksel", |
| 451 | .input_size = 4, |
| 452 | .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, |
| 453 | .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, |
| 454 | NPCM7XX_CLOCK_PLL2D2}, |
| 455 | .offset = 6, |
| 456 | .len = 2, |
| 457 | }, |
| 458 | [NPCM7XX_CLOCK_GFXMSEL] = { |
| 459 | .name = "gfxmksel", |
| 460 | .input_size = 2, |
| 461 | .src_type = {CLKSRC_REF, CLKSRC_PLL}, |
| 462 | .src_index = {0, NPCM7XX_CLOCK_PLL2}, |
| 463 | .offset = 21, |
| 464 | .len = 1, |
| 465 | }, |
| 466 | [NPCM7XX_CLOCK_SUCKSEL] = { |
| 467 | .name = "sucksel", |
| 468 | .input_size = 4, |
| 469 | .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, |
| 470 | .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, |
| 471 | NPCM7XX_CLOCK_PLL2D2}, |
| 472 | .offset = 10, |
| 473 | .len = 2, |
| 474 | }, |
| 475 | }; |
| 476 | |
| 477 | static const DividerInitInfo divider_init_info_list[] = { |
| 478 | [NPCM7XX_CLOCK_PLL1D2] = { |
| 479 | .name = "pll1d2", |
| 480 | .src_type = CLKSRC_PLL, |
| 481 | .src_index = NPCM7XX_CLOCK_PLL1, |
| 482 | .divide = divide_by_constant, |
| 483 | .divisor = 2, |
| 484 | }, |
| 485 | [NPCM7XX_CLOCK_PLL2D2] = { |
| 486 | .name = "pll2d2", |
| 487 | .src_type = CLKSRC_PLL, |
| 488 | .src_index = NPCM7XX_CLOCK_PLL2, |
| 489 | .divide = divide_by_constant, |
| 490 | .divisor = 2, |
| 491 | }, |
| 492 | [NPCM7XX_CLOCK_MC_DIVIDER] = { |
| 493 | .name = "mc-divider", |
| 494 | .src_type = CLKSRC_SEL, |
| 495 | .src_index = NPCM7XX_CLOCK_MCCKSEL, |
| 496 | .divide = divide_by_constant, |
| 497 | .divisor = 2, |
| 498 | .public_name = "mc-clock" |
| 499 | }, |
| 500 | [NPCM7XX_CLOCK_AXI_DIVIDER] = { |
| 501 | .name = "axi-divider", |
| 502 | .src_type = CLKSRC_SEL, |
| 503 | .src_index = NPCM7XX_CLOCK_CPUCKSEL, |
| 504 | .divide = shift_by_reg_divisor, |
| 505 | .reg = NPCM7XX_CLK_CLKDIV1, |
| 506 | .offset = 0, |
| 507 | .len = 1, |
| 508 | .public_name = "clk2" |
| 509 | }, |
| 510 | [NPCM7XX_CLOCK_AHB_DIVIDER] = { |
| 511 | .name = "ahb-divider", |
| 512 | .src_type = CLKSRC_DIV, |
| 513 | .src_index = NPCM7XX_CLOCK_AXI_DIVIDER, |
| 514 | .divide = divide_by_reg_divisor, |
| 515 | .reg = NPCM7XX_CLK_CLKDIV1, |
| 516 | .offset = 26, |
| 517 | .len = 2, |
| 518 | .public_name = "clk4" |
| 519 | }, |
| 520 | [NPCM7XX_CLOCK_AHB3_DIVIDER] = { |
| 521 | .name = "ahb3-divider", |
| 522 | .src_type = CLKSRC_DIV, |
| 523 | .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, |
| 524 | .divide = divide_by_reg_divisor, |
| 525 | .reg = NPCM7XX_CLK_CLKDIV1, |
| 526 | .offset = 6, |
| 527 | .len = 5, |
| 528 | .public_name = "ahb3-spi3-clock" |
| 529 | }, |
| 530 | [NPCM7XX_CLOCK_SPI0_DIVIDER] = { |
| 531 | .name = "spi0-divider", |
| 532 | .src_type = CLKSRC_DIV, |
| 533 | .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, |
| 534 | .divide = divide_by_reg_divisor, |
| 535 | .reg = NPCM7XX_CLK_CLKDIV3, |
| 536 | .offset = 6, |
| 537 | .len = 5, |
| 538 | .public_name = "spi0-clock", |
| 539 | }, |
| 540 | [NPCM7XX_CLOCK_SPIX_DIVIDER] = { |
| 541 | .name = "spix-divider", |
| 542 | .src_type = CLKSRC_DIV, |
| 543 | .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, |
| 544 | .divide = divide_by_reg_divisor, |
| 545 | .reg = NPCM7XX_CLK_CLKDIV3, |
| 546 | .offset = 1, |
| 547 | .len = 5, |
| 548 | .public_name = "spix-clock", |
| 549 | }, |
| 550 | [NPCM7XX_CLOCK_APB1_DIVIDER] = { |
| 551 | .name = "apb1-divider", |
| 552 | .src_type = CLKSRC_DIV, |
| 553 | .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, |
| 554 | .divide = shift_by_reg_divisor, |
| 555 | .reg = NPCM7XX_CLK_CLKDIV2, |
| 556 | .offset = 24, |
| 557 | .len = 2, |
| 558 | .public_name = "apb1-clock", |
| 559 | }, |
| 560 | [NPCM7XX_CLOCK_APB2_DIVIDER] = { |
| 561 | .name = "apb2-divider", |
| 562 | .src_type = CLKSRC_DIV, |
| 563 | .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, |
| 564 | .divide = shift_by_reg_divisor, |
| 565 | .reg = NPCM7XX_CLK_CLKDIV2, |
| 566 | .offset = 26, |
| 567 | .len = 2, |
| 568 | .public_name = "apb2-clock", |
| 569 | }, |
| 570 | [NPCM7XX_CLOCK_APB3_DIVIDER] = { |
| 571 | .name = "apb3-divider", |
| 572 | .src_type = CLKSRC_DIV, |
| 573 | .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, |
| 574 | .divide = shift_by_reg_divisor, |
| 575 | .reg = NPCM7XX_CLK_CLKDIV2, |
| 576 | .offset = 28, |
| 577 | .len = 2, |
| 578 | .public_name = "apb3-clock", |
| 579 | }, |
| 580 | [NPCM7XX_CLOCK_APB4_DIVIDER] = { |
| 581 | .name = "apb4-divider", |
| 582 | .src_type = CLKSRC_DIV, |
| 583 | .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, |
| 584 | .divide = shift_by_reg_divisor, |
| 585 | .reg = NPCM7XX_CLK_CLKDIV2, |
| 586 | .offset = 30, |
| 587 | .len = 2, |
| 588 | .public_name = "apb4-clock", |
| 589 | }, |
| 590 | [NPCM7XX_CLOCK_APB5_DIVIDER] = { |
| 591 | .name = "apb5-divider", |
| 592 | .src_type = CLKSRC_DIV, |
| 593 | .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, |
| 594 | .divide = shift_by_reg_divisor, |
| 595 | .reg = NPCM7XX_CLK_CLKDIV2, |
| 596 | .offset = 22, |
| 597 | .len = 2, |
| 598 | .public_name = "apb5-clock", |
| 599 | }, |
| 600 | [NPCM7XX_CLOCK_CLKOUT_DIVIDER] = { |
| 601 | .name = "clkout-divider", |
| 602 | .src_type = CLKSRC_SEL, |
| 603 | .src_index = NPCM7XX_CLOCK_CLKOUTSEL, |
| 604 | .divide = divide_by_reg_divisor, |
| 605 | .reg = NPCM7XX_CLK_CLKDIV2, |
| 606 | .offset = 16, |
| 607 | .len = 5, |
| 608 | .public_name = "clkout", |
| 609 | }, |
| 610 | [NPCM7XX_CLOCK_UART_DIVIDER] = { |
| 611 | .name = "uart-divider", |
| 612 | .src_type = CLKSRC_SEL, |
| 613 | .src_index = NPCM7XX_CLOCK_UARTCKSEL, |
| 614 | .divide = divide_by_reg_divisor, |
| 615 | .reg = NPCM7XX_CLK_CLKDIV1, |
| 616 | .offset = 16, |
| 617 | .len = 5, |
| 618 | .public_name = "uart-clock", |
| 619 | }, |
| 620 | [NPCM7XX_CLOCK_TIMER_DIVIDER] = { |
| 621 | .name = "timer-divider", |
| 622 | .src_type = CLKSRC_SEL, |
| 623 | .src_index = NPCM7XX_CLOCK_TIMCKSEL, |
| 624 | .divide = divide_by_reg_divisor, |
| 625 | .reg = NPCM7XX_CLK_CLKDIV1, |
| 626 | .offset = 21, |
| 627 | .len = 5, |
| 628 | .public_name = "timer-clock", |
| 629 | }, |
| 630 | [NPCM7XX_CLOCK_ADC_DIVIDER] = { |
| 631 | .name = "adc-divider", |
| 632 | .src_type = CLKSRC_DIV, |
| 633 | .src_index = NPCM7XX_CLOCK_TIMER_DIVIDER, |
| 634 | .divide = shift_by_reg_divisor, |
| 635 | .reg = NPCM7XX_CLK_CLKDIV1, |
| 636 | .offset = 28, |
| 637 | .len = 3, |
| 638 | .public_name = "adc-clock", |
| 639 | }, |
| 640 | [NPCM7XX_CLOCK_MMC_DIVIDER] = { |
| 641 | .name = "mmc-divider", |
| 642 | .src_type = CLKSRC_SEL, |
| 643 | .src_index = NPCM7XX_CLOCK_SDCKSEL, |
| 644 | .divide = divide_by_reg_divisor, |
| 645 | .reg = NPCM7XX_CLK_CLKDIV1, |
| 646 | .offset = 11, |
| 647 | .len = 5, |
| 648 | .public_name = "mmc-clock", |
| 649 | }, |
| 650 | [NPCM7XX_CLOCK_SDHC_DIVIDER] = { |
| 651 | .name = "sdhc-divider", |
| 652 | .src_type = CLKSRC_SEL, |
| 653 | .src_index = NPCM7XX_CLOCK_SDCKSEL, |
| 654 | .divide = divide_by_reg_divisor_times_2, |
| 655 | .reg = NPCM7XX_CLK_CLKDIV2, |
| 656 | .offset = 0, |
| 657 | .len = 4, |
| 658 | .public_name = "sdhc-clock", |
| 659 | }, |
| 660 | [NPCM7XX_CLOCK_GFXM_DIVIDER] = { |
| 661 | .name = "gfxm-divider", |
| 662 | .src_type = CLKSRC_SEL, |
| 663 | .src_index = NPCM7XX_CLOCK_GFXMSEL, |
| 664 | .divide = divide_by_constant, |
| 665 | .divisor = 3, |
| 666 | .public_name = "gfxm-clock", |
| 667 | }, |
| 668 | [NPCM7XX_CLOCK_UTMI_DIVIDER] = { |
| 669 | .name = "utmi-divider", |
| 670 | .src_type = CLKSRC_SEL, |
| 671 | .src_index = NPCM7XX_CLOCK_SUCKSEL, |
| 672 | .divide = divide_by_reg_divisor, |
| 673 | .reg = NPCM7XX_CLK_CLKDIV2, |
| 674 | .offset = 8, |
| 675 | .len = 5, |
| 676 | .public_name = "utmi-clock", |
| 677 | }, |
| 678 | }; |
| 679 | |
| 680 | static void npcm7xx_clk_update_pll_cb(void *opaque, ClockEvent event) |
| 681 | { |
| 682 | npcm7xx_clk_update_pll(opaque); |
| 683 | } |
| 684 | |
| 685 | static void npcm7xx_clk_pll_init(Object *obj) |
| 686 | { |
| 687 | NPCM7xxClockPLLState *pll = NPCM7XX_CLOCK_PLL(obj); |
| 688 | |
| 689 | pll->clock_in = qdev_init_clock_in(DEVICE(pll), "clock-in", |
| 690 | npcm7xx_clk_update_pll_cb, pll, |
| 691 | ClockUpdate); |
| 692 | pll->clock_out = qdev_init_clock_out(DEVICE(pll), "clock-out"); |
| 693 | } |
| 694 | |
| 695 | static void npcm7xx_clk_update_sel_cb(void *opaque, ClockEvent event) |
| 696 | { |
| 697 | npcm7xx_clk_update_sel(opaque); |
| 698 | } |
| 699 | |
| 700 | static void npcm7xx_clk_sel_init(Object *obj) |
| 701 | { |
| 702 | int i; |
| 703 | NPCM7xxClockSELState *sel = NPCM7XX_CLOCK_SEL(obj); |
| 704 | |
| 705 | for (i = 0; i < NPCM7XX_CLK_SEL_MAX_INPUT; ++i) { |
| 706 | g_autofree char *s = g_strdup_printf("clock-in[%d]", i); |
| 707 | sel->clock_in[i] = qdev_init_clock_in(DEVICE(sel), s, |
| 708 | npcm7xx_clk_update_sel_cb, sel, ClockUpdate); |
| 709 | } |
| 710 | sel->clock_out = qdev_init_clock_out(DEVICE(sel), "clock-out"); |
| 711 | } |
| 712 | |
| 713 | static void npcm7xx_clk_update_divider_cb(void *opaque, ClockEvent event) |
| 714 | { |
| 715 | npcm7xx_clk_update_divider(opaque); |
| 716 | } |
| 717 | |
| 718 | static void npcm7xx_clk_divider_init(Object *obj) |
| 719 | { |
| 720 | NPCM7xxClockDividerState *div = NPCM7XX_CLOCK_DIVIDER(obj); |
| 721 | |
| 722 | div->clock_in = qdev_init_clock_in(DEVICE(div), "clock-in", |
| 723 | npcm7xx_clk_update_divider_cb, |
| 724 | div, ClockUpdate); |
| 725 | div->clock_out = qdev_init_clock_out(DEVICE(div), "clock-out"); |
| 726 | } |
| 727 | |
| 728 | static void npcm7xx_init_clock_pll(NPCM7xxClockPLLState *pll, |
| 729 | NPCMCLKState *clk, const PLLInitInfo *init_info) |
| 730 | { |
| 731 | pll->name = init_info->name; |
| 732 | pll->clk = clk; |
| 733 | pll->reg = init_info->reg; |
| 734 | if (init_info->public_name != NULL) { |
| 735 | qdev_alias_clock(DEVICE(pll), "clock-out", DEVICE(clk), |
| 736 | init_info->public_name); |
| 737 | } |
| 738 | } |
| 739 | |
| 740 | static void npcm7xx_init_clock_sel(NPCM7xxClockSELState *sel, |
| 741 | NPCMCLKState *clk, const SELInitInfo *init_info) |
| 742 | { |
| 743 | int input_size = init_info->input_size; |
| 744 | |
| 745 | sel->name = init_info->name; |
| 746 | sel->clk = clk; |
| 747 | sel->input_size = init_info->input_size; |
| 748 | g_assert(input_size <= NPCM7XX_CLK_SEL_MAX_INPUT); |
| 749 | sel->offset = init_info->offset; |
| 750 | sel->len = init_info->len; |
| 751 | if (init_info->public_name != NULL) { |
| 752 | qdev_alias_clock(DEVICE(sel), "clock-out", DEVICE(clk), |
| 753 | init_info->public_name); |
| 754 | } |
| 755 | } |
| 756 | |
| 757 | static void npcm7xx_init_clock_divider(NPCM7xxClockDividerState *div, |
| 758 | NPCMCLKState *clk, const DividerInitInfo *init_info) |
| 759 | { |
| 760 | div->name = init_info->name; |
| 761 | div->clk = clk; |
| 762 | |
| 763 | div->divide = init_info->divide; |
| 764 | if (div->divide == divide_by_constant) { |
| 765 | div->divisor = init_info->divisor; |
| 766 | } else { |
| 767 | div->reg = init_info->reg; |
| 768 | div->offset = init_info->offset; |
| 769 | div->len = init_info->len; |
| 770 | } |
| 771 | if (init_info->public_name != NULL) { |
| 772 | qdev_alias_clock(DEVICE(div), "clock-out", DEVICE(clk), |
| 773 | init_info->public_name); |
| 774 | } |
| 775 | } |
| 776 | |
| 777 | static Clock *npcm7xx_get_clock(NPCMCLKState *clk, ClockSrcType type, |
| 778 | int index) |
| 779 | { |
| 780 | switch (type) { |
| 781 | case CLKSRC_REF: |
| 782 | return clk->clkref; |
| 783 | case CLKSRC_PLL: |
| 784 | return clk->plls[index].clock_out; |
| 785 | case CLKSRC_SEL: |
| 786 | return clk->sels[index].clock_out; |
| 787 | case CLKSRC_DIV: |
| 788 | return clk->dividers[index].clock_out; |
| 789 | default: |
| 790 | g_assert_not_reached(); |
| 791 | } |
| 792 | } |
| 793 | |
| 794 | static void npcm7xx_connect_clocks(NPCMCLKState *clk) |
| 795 | { |
| 796 | int i, j; |
| 797 | Clock *src; |
| 798 | |
| 799 | for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { |
| 800 | src = npcm7xx_get_clock(clk, pll_init_info_list[i].src_type, |
| 801 | pll_init_info_list[i].src_index); |
| 802 | clock_set_source(clk->plls[i].clock_in, src); |
| 803 | } |
| 804 | for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { |
| 805 | for (j = 0; j < sel_init_info_list[i].input_size; ++j) { |
| 806 | src = npcm7xx_get_clock(clk, sel_init_info_list[i].src_type[j], |
| 807 | sel_init_info_list[i].src_index[j]); |
| 808 | clock_set_source(clk->sels[i].clock_in[j], src); |
| 809 | } |
| 810 | } |
| 811 | for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { |
| 812 | src = npcm7xx_get_clock(clk, divider_init_info_list[i].src_type, |
| 813 | divider_init_info_list[i].src_index); |
| 814 | clock_set_source(clk->dividers[i].clock_in, src); |
| 815 | } |
| 816 | } |
| 817 | |
| 818 | static uint64_t npcm_clk_read(void *opaque, hwaddr offset, unsigned size) |
| 819 | { |
| 820 | uint32_t reg = offset / sizeof(uint32_t); |
| 821 | NPCMCLKState *s = opaque; |
| 822 | NPCMCLKClass *c = NPCM_CLK_GET_CLASS(s); |
| 823 | int64_t now_ns; |
| 824 | uint32_t value = 0; |
| 825 | |
| 826 | if (reg >= c->nr_regs) { |
| 827 | qemu_log_mask(LOG_GUEST_ERROR, |
| 828 | "%s: offset 0x%04" HWADDR_PRIx " out of range\n", |
| 829 | __func__, offset); |
| 830 | return 0; |
| 831 | } |
| 832 | |
| 833 | switch (reg) { |
| 834 | case NPCM7XX_CLK_SWRSTR: |
| 835 | qemu_log_mask(LOG_GUEST_ERROR, |
| 836 | "%s: register @ 0x%04" HWADDR_PRIx " is write-only\n", |
| 837 | __func__, offset); |
| 838 | break; |
| 839 | |
| 840 | case NPCM7XX_CLK_SECCNT: |
| 841 | now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); |
| 842 | value = (now_ns - s->ref_ns) / NANOSECONDS_PER_SECOND; |
| 843 | break; |
| 844 | |
| 845 | case NPCM7XX_CLK_CNTR25M: |
| 846 | now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); |
| 847 | /* |
| 848 | * This register counts 25 MHz cycles, updating every 640 ns. It rolls |
| 849 | * over to zero every second. |
| 850 | * |
| 851 | * The 4 LSBs are always zero: (1e9 / 640) << 4 = 25000000. |
| 852 | */ |
| 853 | value = (((now_ns - s->ref_ns) / 640) << 4) % NPCM7XX_CLOCK_REF_HZ; |
| 854 | break; |
| 855 | |
| 856 | default: |
| 857 | value = s->regs[reg]; |
| 858 | break; |
| 859 | }; |
| 860 | |
| 861 | trace_npcm_clk_read(offset, value); |
| 862 | |
| 863 | return value; |
| 864 | } |
| 865 | |
| 866 | static void npcm_clk_write(void *opaque, hwaddr offset, |
| 867 | uint64_t v, unsigned size) |
| 868 | { |
| 869 | uint32_t reg = offset / sizeof(uint32_t); |
| 870 | NPCMCLKState *s = opaque; |
| 871 | NPCMCLKClass *c = NPCM_CLK_GET_CLASS(s); |
| 872 | uint32_t value = v; |
| 873 | |
| 874 | trace_npcm_clk_write(offset, value); |
| 875 | |
| 876 | if (reg >= c->nr_regs) { |
| 877 | qemu_log_mask(LOG_GUEST_ERROR, |
| 878 | "%s: offset 0x%04" HWADDR_PRIx " out of range\n", |
| 879 | __func__, offset); |
| 880 | return; |
| 881 | } |
| 882 | |
| 883 | switch (reg) { |
| 884 | case NPCM7XX_CLK_SWRSTR: |
| 885 | qemu_log_mask(LOG_UNIMP, "%s: SW reset not implemented: 0x%02x\n", |
| 886 | __func__, value); |
| 887 | value = 0; |
| 888 | break; |
| 889 | |
| 890 | case NPCM7XX_CLK_PLLCON0: |
| 891 | case NPCM7XX_CLK_PLLCON1: |
| 892 | case NPCM7XX_CLK_PLLCON2: |
| 893 | case NPCM7XX_CLK_PLLCONG: |
| 894 | if (value & PLLCON_PWDEN) { |
| 895 | /* Power down -- clear lock and indicate loss of lock */ |
| 896 | value &= ~PLLCON_LOKI; |
| 897 | value |= PLLCON_LOKS; |
| 898 | } else { |
| 899 | /* Normal mode -- assume always locked */ |
| 900 | value |= PLLCON_LOKI; |
| 901 | /* Keep LOKS unchanged unless cleared by writing 1 */ |
| 902 | if (value & PLLCON_LOKS) { |
| 903 | value &= ~PLLCON_LOKS; |
| 904 | } else { |
| 905 | value |= (value & PLLCON_LOKS); |
| 906 | } |
| 907 | } |
| 908 | /* Only update PLL when it is locked. */ |
| 909 | if (value & PLLCON_LOKI) { |
| 910 | npcm7xx_clk_update_pll(&s->plls[find_pll_by_reg(reg)]); |
| 911 | } |
| 912 | break; |
| 913 | |
| 914 | case NPCM7XX_CLK_CLKSEL: |
| 915 | npcm7xx_clk_update_all_sels(s); |
| 916 | break; |
| 917 | |
| 918 | case NPCM7XX_CLK_CLKDIV1: |
| 919 | case NPCM7XX_CLK_CLKDIV2: |
| 920 | case NPCM7XX_CLK_CLKDIV3: |
| 921 | npcm7xx_clk_update_all_dividers(s); |
| 922 | break; |
| 923 | |
| 924 | case NPCM7XX_CLK_CNTR25M: |
| 925 | qemu_log_mask(LOG_GUEST_ERROR, |
| 926 | "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n", |
| 927 | __func__, offset); |
| 928 | return; |
| 929 | } |
| 930 | |
| 931 | s->regs[reg] = value; |
| 932 | } |
| 933 | |
| 934 | /* Perform reset action triggered by a watchdog */ |
| 935 | static void npcm7xx_clk_perform_watchdog_reset(void *opaque, int n, |
| 936 | int level) |
| 937 | { |
| 938 | NPCMCLKState *clk = NPCM_CLK(opaque); |
| 939 | uint32_t rcr; |
| 940 | |
| 941 | g_assert(n >= 0 && n <= NPCM7XX_NR_WATCHDOGS); |
| 942 | rcr = clk->regs[NPCM7XX_CLK_WD0RCR + n]; |
| 943 | if (rcr & NPCM7XX_CLK_WDRCR_CA9C) { |
| 944 | watchdog_perform_action(); |
| 945 | } else { |
| 946 | qemu_log_mask(LOG_UNIMP, |
| 947 | "%s: only CPU reset is implemented. (requested 0x%" PRIx32")\n", |
| 948 | __func__, rcr); |
| 949 | } |
| 950 | } |
| 951 | |
| 952 | static const struct MemoryRegionOps npcm_clk_ops = { |
| 953 | .read = npcm_clk_read, |
| 954 | .write = npcm_clk_write, |
| 955 | .endianness = DEVICE_LITTLE_ENDIAN, |
| 956 | .valid = { |
| 957 | .min_access_size = 4, |
| 958 | .max_access_size = 4, |
| 959 | .unaligned = false, |
| 960 | }, |
| 961 | }; |
| 962 | |
| 963 | static void npcm_clk_enter_reset(Object *obj, ResetType type) |
| 964 | { |
| 965 | NPCMCLKState *s = NPCM_CLK(obj); |
| 966 | NPCMCLKClass *c = NPCM_CLK_GET_CLASS(s); |
| 967 | |
| 968 | size_t sizeof_regs = c->nr_regs * sizeof(uint32_t); |
| 969 | g_assert(sizeof(s->regs) >= sizeof_regs); |
| 970 | memcpy(s->regs, c->cold_reset_values, sizeof_regs); |
| 971 | s->ref_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); |
| 972 | npcm7xx_clk_update_all_clocks(s); |
| 973 | /* |
| 974 | * A small number of registers need to be reset on a core domain reset, |
| 975 | * but no such reset type exists yet. |
| 976 | */ |
| 977 | } |
| 978 | |
| 979 | static void npcm7xx_clk_init_clock_hierarchy(NPCMCLKState *s) |
| 980 | { |
| 981 | int i; |
| 982 | |
| 983 | s->clkref = qdev_init_clock_in(DEVICE(s), "clkref", NULL, NULL, 0); |
| 984 | |
| 985 | /* First pass: init all converter modules */ |
| 986 | QEMU_BUILD_BUG_ON(ARRAY_SIZE(pll_init_info_list) != NPCM7XX_CLOCK_NR_PLLS); |
| 987 | QEMU_BUILD_BUG_ON(ARRAY_SIZE(sel_init_info_list) != NPCM7XX_CLOCK_NR_SELS); |
| 988 | QEMU_BUILD_BUG_ON(ARRAY_SIZE(divider_init_info_list) |
| 989 | != NPCM7XX_CLOCK_NR_DIVIDERS); |
| 990 | for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { |
| 991 | object_initialize_child(OBJECT(s), pll_init_info_list[i].name, |
| 992 | &s->plls[i], TYPE_NPCM7XX_CLOCK_PLL); |
| 993 | npcm7xx_init_clock_pll(&s->plls[i], s, |
| 994 | &pll_init_info_list[i]); |
| 995 | } |
| 996 | for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { |
| 997 | object_initialize_child(OBJECT(s), sel_init_info_list[i].name, |
| 998 | &s->sels[i], TYPE_NPCM7XX_CLOCK_SEL); |
| 999 | npcm7xx_init_clock_sel(&s->sels[i], s, |
| 1000 | &sel_init_info_list[i]); |
| 1001 | } |
| 1002 | for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { |
| 1003 | object_initialize_child(OBJECT(s), divider_init_info_list[i].name, |
| 1004 | &s->dividers[i], TYPE_NPCM7XX_CLOCK_DIVIDER); |
| 1005 | npcm7xx_init_clock_divider(&s->dividers[i], s, |
| 1006 | ÷r_init_info_list[i]); |
| 1007 | } |
| 1008 | |
| 1009 | /* Second pass: connect converter modules */ |
| 1010 | npcm7xx_connect_clocks(s); |
| 1011 | |
| 1012 | clock_update_hz(s->clkref, NPCM7XX_CLOCK_REF_HZ); |
| 1013 | } |
| 1014 | |
| 1015 | static void npcm_clk_init(Object *obj) |
| 1016 | { |
| 1017 | NPCMCLKState *s = NPCM_CLK(obj); |
| 1018 | |
| 1019 | memory_region_init_io(&s->iomem, obj, &npcm_clk_ops, s, |
| 1020 | TYPE_NPCM_CLK, 4 * KiB); |
| 1021 | sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); |
| 1022 | } |
| 1023 | |
| 1024 | static int npcm_clk_post_load(void *opaque, int version_id) |
| 1025 | { |
| 1026 | if (version_id >= 1) { |
| 1027 | NPCMCLKState *clk = opaque; |
| 1028 | |
| 1029 | npcm7xx_clk_update_all_clocks(clk); |
| 1030 | } |
| 1031 | |
| 1032 | return 0; |
| 1033 | } |
| 1034 | |
| 1035 | static void npcm_clk_realize(DeviceState *dev, Error **errp) |
| 1036 | { |
| 1037 | int i; |
| 1038 | NPCMCLKState *s = NPCM_CLK(dev); |
| 1039 | |
| 1040 | qdev_init_gpio_in_named(DEVICE(s), npcm7xx_clk_perform_watchdog_reset, |
| 1041 | NPCM7XX_WATCHDOG_RESET_GPIO_IN, NPCM7XX_NR_WATCHDOGS); |
| 1042 | npcm7xx_clk_init_clock_hierarchy(s); |
| 1043 | |
| 1044 | /* Realize child devices */ |
| 1045 | for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { |
| 1046 | if (!qdev_realize(DEVICE(&s->plls[i]), NULL, errp)) { |
| 1047 | return; |
| 1048 | } |
| 1049 | } |
| 1050 | for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { |
| 1051 | if (!qdev_realize(DEVICE(&s->sels[i]), NULL, errp)) { |
| 1052 | return; |
| 1053 | } |
| 1054 | } |
| 1055 | for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { |
| 1056 | if (!qdev_realize(DEVICE(&s->dividers[i]), NULL, errp)) { |
| 1057 | return; |
| 1058 | } |
| 1059 | } |
| 1060 | } |
| 1061 | |
| 1062 | static const VMStateDescription vmstate_npcm7xx_clk_pll = { |
| 1063 | .name = "npcm7xx-clock-pll", |
| 1064 | .version_id = 0, |
| 1065 | .minimum_version_id = 0, |
| 1066 | .fields = (const VMStateField[]) { |
| 1067 | VMSTATE_CLOCK(clock_in, NPCM7xxClockPLLState), |
| 1068 | VMSTATE_END_OF_LIST(), |
| 1069 | }, |
| 1070 | }; |
| 1071 | |
| 1072 | static const VMStateDescription vmstate_npcm7xx_clk_sel = { |
| 1073 | .name = "npcm7xx-clock-sel", |
| 1074 | .version_id = 0, |
| 1075 | .minimum_version_id = 0, |
| 1076 | .fields = (const VMStateField[]) { |
| 1077 | VMSTATE_ARRAY_OF_POINTER_TO_STRUCT(clock_in, NPCM7xxClockSELState, |
| 1078 | NPCM7XX_CLK_SEL_MAX_INPUT, 0, vmstate_clock, Clock), |
| 1079 | VMSTATE_END_OF_LIST(), |
| 1080 | }, |
| 1081 | }; |
| 1082 | |
| 1083 | static const VMStateDescription vmstate_npcm7xx_clk_divider = { |
| 1084 | .name = "npcm7xx-clock-divider", |
| 1085 | .version_id = 0, |
| 1086 | .minimum_version_id = 0, |
| 1087 | .fields = (const VMStateField[]) { |
| 1088 | VMSTATE_CLOCK(clock_in, NPCM7xxClockDividerState), |
| 1089 | VMSTATE_END_OF_LIST(), |
| 1090 | }, |
| 1091 | }; |
| 1092 | |
| 1093 | static const VMStateDescription vmstate_npcm_clk = { |
| 1094 | .name = "npcm-clk", |
| 1095 | .version_id = 3, |
| 1096 | .minimum_version_id = 3, |
| 1097 | .post_load = npcm_clk_post_load, |
| 1098 | .fields = (const VMStateField[]) { |
| 1099 | VMSTATE_UINT32_ARRAY(regs, NPCMCLKState, NPCM_CLK_MAX_NR_REGS), |
| 1100 | VMSTATE_INT64(ref_ns, NPCMCLKState), |
| 1101 | VMSTATE_CLOCK(clkref, NPCMCLKState), |
| 1102 | VMSTATE_END_OF_LIST(), |
| 1103 | }, |
| 1104 | }; |
| 1105 | |
| 1106 | static void npcm7xx_clk_pll_class_init(ObjectClass *klass, const void *data) |
| 1107 | { |
| 1108 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 1109 | |
| 1110 | dc->desc = "NPCM7xx Clock PLL Module"; |
| 1111 | dc->vmsd = &vmstate_npcm7xx_clk_pll; |
| 1112 | /* Reason: Part of NPCMCLKState component */ |
| 1113 | dc->user_creatable = false; |
| 1114 | } |
| 1115 | |
| 1116 | static void npcm7xx_clk_sel_class_init(ObjectClass *klass, const void *data) |
| 1117 | { |
| 1118 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 1119 | |
| 1120 | dc->desc = "NPCM7xx Clock SEL Module"; |
| 1121 | dc->vmsd = &vmstate_npcm7xx_clk_sel; |
| 1122 | /* Reason: Part of NPCMCLKState component */ |
| 1123 | dc->user_creatable = false; |
| 1124 | } |
| 1125 | |
| 1126 | static void npcm7xx_clk_divider_class_init(ObjectClass *klass, const void *data) |
| 1127 | { |
| 1128 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 1129 | |
| 1130 | dc->desc = "NPCM7xx Clock Divider Module"; |
| 1131 | dc->vmsd = &vmstate_npcm7xx_clk_divider; |
| 1132 | /* Reason: Part of NPCMCLKState component */ |
| 1133 | dc->user_creatable = false; |
| 1134 | } |
| 1135 | |
| 1136 | static void npcm_clk_class_init(ObjectClass *klass, const void *data) |
| 1137 | { |
| 1138 | ResettableClass *rc = RESETTABLE_CLASS(klass); |
| 1139 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 1140 | |
| 1141 | dc->vmsd = &vmstate_npcm_clk; |
| 1142 | dc->realize = npcm_clk_realize; |
| 1143 | rc->phases.enter = npcm_clk_enter_reset; |
| 1144 | } |
| 1145 | |
| 1146 | static void npcm7xx_clk_class_init(ObjectClass *klass, const void *data) |
| 1147 | { |
| 1148 | NPCMCLKClass *c = NPCM_CLK_CLASS(klass); |
| 1149 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 1150 | |
| 1151 | dc->desc = "NPCM7xx Clock Control Registers"; |
| 1152 | c->nr_regs = NPCM7XX_CLK_NR_REGS; |
| 1153 | c->cold_reset_values = npcm7xx_cold_reset_values; |
| 1154 | } |
| 1155 | |
| 1156 | static void npcm8xx_clk_class_init(ObjectClass *klass, const void *data) |
| 1157 | { |
| 1158 | NPCMCLKClass *c = NPCM_CLK_CLASS(klass); |
| 1159 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 1160 | |
| 1161 | dc->desc = "NPCM8xx Clock Control Registers"; |
| 1162 | c->nr_regs = NPCM8XX_CLK_NR_REGS; |
| 1163 | c->cold_reset_values = npcm8xx_cold_reset_values; |
| 1164 | } |
| 1165 | |
| 1166 | static const TypeInfo npcm7xx_clk_pll_info = { |
| 1167 | .name = TYPE_NPCM7XX_CLOCK_PLL, |
| 1168 | .parent = TYPE_DEVICE, |
| 1169 | .instance_size = sizeof(NPCM7xxClockPLLState), |
| 1170 | .instance_init = npcm7xx_clk_pll_init, |
| 1171 | .class_init = npcm7xx_clk_pll_class_init, |
| 1172 | }; |
| 1173 | |
| 1174 | static const TypeInfo npcm7xx_clk_sel_info = { |
| 1175 | .name = TYPE_NPCM7XX_CLOCK_SEL, |
| 1176 | .parent = TYPE_DEVICE, |
| 1177 | .instance_size = sizeof(NPCM7xxClockSELState), |
| 1178 | .instance_init = npcm7xx_clk_sel_init, |
| 1179 | .class_init = npcm7xx_clk_sel_class_init, |
| 1180 | }; |
| 1181 | |
| 1182 | static const TypeInfo npcm7xx_clk_divider_info = { |
| 1183 | .name = TYPE_NPCM7XX_CLOCK_DIVIDER, |
| 1184 | .parent = TYPE_DEVICE, |
| 1185 | .instance_size = sizeof(NPCM7xxClockDividerState), |
| 1186 | .instance_init = npcm7xx_clk_divider_init, |
| 1187 | .class_init = npcm7xx_clk_divider_class_init, |
| 1188 | }; |
| 1189 | |
| 1190 | static const TypeInfo npcm_clk_info = { |
| 1191 | .name = TYPE_NPCM_CLK, |
| 1192 | .parent = TYPE_SYS_BUS_DEVICE, |
| 1193 | .instance_size = sizeof(NPCMCLKState), |
| 1194 | .instance_init = npcm_clk_init, |
| 1195 | .class_size = sizeof(NPCMCLKClass), |
| 1196 | .class_init = npcm_clk_class_init, |
| 1197 | .abstract = true, |
| 1198 | }; |
| 1199 | |
| 1200 | static const TypeInfo npcm7xx_clk_info = { |
| 1201 | .name = TYPE_NPCM7XX_CLK, |
| 1202 | .parent = TYPE_NPCM_CLK, |
| 1203 | .class_init = npcm7xx_clk_class_init, |
| 1204 | }; |
| 1205 | |
| 1206 | static const TypeInfo npcm8xx_clk_info = { |
| 1207 | .name = TYPE_NPCM8XX_CLK, |
| 1208 | .parent = TYPE_NPCM_CLK, |
| 1209 | .class_init = npcm8xx_clk_class_init, |
| 1210 | }; |
| 1211 | |
| 1212 | static void npcm7xx_clk_register_type(void) |
| 1213 | { |
| 1214 | type_register_static(&npcm7xx_clk_pll_info); |
| 1215 | type_register_static(&npcm7xx_clk_sel_info); |
| 1216 | type_register_static(&npcm7xx_clk_divider_info); |
| 1217 | type_register_static(&npcm_clk_info); |
| 1218 | type_register_static(&npcm7xx_clk_info); |
| 1219 | type_register_static(&npcm8xx_clk_info); |
| 1220 | } |
| 1221 | type_init(npcm7xx_clk_register_type); |