Message ID | 20201215001312.3120777-2-wuhaotsh@google.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Additional NPCM7xx devices | expand |
On Mon, Dec 14, 2020 at 4:13 PM Hao Wu <wuhaotsh@google.com> wrote: > This patch allows NPCM7XX CLK module to compute clocks that are used by > other NPCM7XX modules. > > Add a new struct NPCM7xxClockConverterState which represents a > single converter. Each clock converter in CLK module represents one > converter in NPCM7XX CLK Module(PLL, SEL or Divider). Each converter > takes one or more input clocks and converts them into one output clock. > They form a clock hierarchy in the CLK module and are responsible for > outputing clocks for various other modules in an NPCM7XX SoC. > > Each converter has a function pointer called "convert" which represents > the unique logic for that converter. > > The clock contains two initialization information: ConverterInitInfo and > ConverterConnectionInfo. They represent the vertices and edges in the > clock diagram respectively. > > Reviewed-by: Havard Skinnemoen <hskinnemoen@google.com> > Reviewed-by: Tyrone Ting <kfting@nuvoton.com> > Signed-off-by: Hao Wu <wuhaotsh@google.com> > --- > hw/misc/npcm7xx_clk.c | 795 +++++++++++++++++++++++++++++++++- > include/hw/misc/npcm7xx_clk.h | 140 +++++- > 2 files changed, 927 insertions(+), 8 deletions(-) > > diff --git a/hw/misc/npcm7xx_clk.c b/hw/misc/npcm7xx_clk.c > index 6732437fe2..48bc9bdda5 100644 > --- a/hw/misc/npcm7xx_clk.c > +++ b/hw/misc/npcm7xx_clk.c > @@ -18,6 +18,7 @@ > > #include "hw/misc/npcm7xx_clk.h" > #include "hw/timer/npcm7xx_timer.h" > +#include "hw/qdev-clock.h" > #include "migration/vmstate.h" > #include "qemu/error-report.h" > #include "qemu/log.h" > @@ -27,9 +28,22 @@ > #include "trace.h" > #include "sysemu/watchdog.h" > > +/* > + * The reference clock hz, and the SECCNT and CNTR25M registers in this > module, > + * is always 25 MHz. > + */ > +#define NPCM7XX_CLOCK_REF_HZ (25000000) > + > +/* Register Field Definitions */ > +#define NPCM7XX_CLK_WDRCR_CA9C BIT(0) /* Cortex A9 Cores */ > + > #define PLLCON_LOKI BIT(31) > #define PLLCON_LOKS BIT(30) > #define PLLCON_PWDEN BIT(12) > +#define PLLCON_FBDV(con) extract32((con), 16, 12) > +#define PLLCON_OTDV2(con) extract32((con), 13, 3) > +#define PLLCON_OTDV1(con) extract32((con), 8, 3) > +#define PLLCON_INDV(con) extract32((con), 0, 6) > > enum NPCM7xxCLKRegisters { > NPCM7XX_CLK_CLKEN1, > @@ -89,12 +103,609 @@ static const uint32_t > cold_reset_values[NPCM7XX_CLK_NR_REGS] = { > [NPCM7XX_CLK_AHBCKFI] = 0x000000c8, > }; > > -/* Register Field Definitions */ > -#define NPCM7XX_CLK_WDRCR_CA9C BIT(0) /* Cortex A9 Cores */ > - > /* The number of watchdogs that can trigger a reset. */ > #define NPCM7XX_NR_WATCHDOGS (3) > > +/* Clock converter functions */ > + > +#define TYPE_NPCM7XX_CLOCK_PLL "npcm7xx-clock-pll" > +#define NPCM7XX_CLOCK_PLL(obj) OBJECT_CHECK(NPCM7xxClockPLLState, \ > + (obj), TYPE_NPCM7XX_CLOCK_PLL) > +#define TYPE_NPCM7XX_CLOCK_SEL "npcm7xx-clock-sel" > +#define NPCM7XX_CLOCK_SEL(obj) OBJECT_CHECK(NPCM7xxClockSELState, \ > + (obj), TYPE_NPCM7XX_CLOCK_SEL) > +#define TYPE_NPCM7XX_CLOCK_DIVIDER "npcm7xx-clock-divider" > +#define NPCM7XX_CLOCK_DIVIDER(obj) OBJECT_CHECK(NPCM7xxClockDividerState, > \ > + (obj), TYPE_NPCM7XX_CLOCK_DIVIDER) > + > +static void npcm7xx_clk_update_pll(void *opaque) > +{ > + NPCM7xxClockPLLState *s = opaque; > + uint32_t con = s->clk->regs[s->reg]; > + uint64_t freq; > + > + /* The PLL is grounded if it is not locked yet. */ > + if (con & PLLCON_LOKI) { > + freq = clock_get_hz(s->clock_in); > + freq *= PLLCON_FBDV(con); > + freq /= PLLCON_INDV(con) * PLLCON_OTDV1(con) * PLLCON_OTDV2(con); > + } else { > + freq = 0; > + } > + > + clock_update_hz(s->clock_out, freq); > +} > + > +static void npcm7xx_clk_update_sel(void *opaque) > +{ > + NPCM7xxClockSELState *s = opaque; > + uint32_t index = extract32(s->clk->regs[NPCM7XX_CLK_CLKSEL], > s->offset, > + s->len); > + > + if (index >= s->input_size) { > + qemu_log_mask(LOG_GUEST_ERROR, > + "%s: SEL index: %u out of range\n", > + __func__, index); > + index = 0; > + } > + clock_update_hz(s->clock_out, clock_get_hz(s->clock_in[index])); > +} > + > +static void npcm7xx_clk_update_divider(void *opaque) > +{ > + NPCM7xxClockDividerState *s = opaque; > + uint32_t freq; > + > + freq = s->divide(s); > + clock_update_hz(s->clock_out, freq); > +} > + > +static uint32_t divide_by_constant(NPCM7xxClockDividerState *s) > +{ > + return clock_get_hz(s->clock_in) / s->divisor; > +} > + > +static uint32_t divide_by_reg_divisor(NPCM7xxClockDividerState *s) > +{ > + return clock_get_hz(s->clock_in) / > + (extract32(s->clk->regs[s->reg], s->offset, s->len) + 1); > +} > + > +static uint32_t divide_by_reg_divisor_times_2(NPCM7xxClockDividerState *s) > +{ > + return divide_by_reg_divisor(s) / 2; > +} > + > +static uint32_t shift_by_reg_divisor(NPCM7xxClockDividerState *s) > +{ > + return clock_get_hz(s->clock_in) >> > + extract32(s->clk->regs[s->reg], s->offset, s->len); > +} > + > +static NPCM7xxClockPLL find_pll_by_reg(enum NPCM7xxCLKRegisters reg) > +{ > + switch (reg) { > + case NPCM7XX_CLK_PLLCON0: > + return NPCM7XX_CLOCK_PLL0; > + case NPCM7XX_CLK_PLLCON1: > + return NPCM7XX_CLOCK_PLL1; > + case NPCM7XX_CLK_PLLCON2: > + return NPCM7XX_CLOCK_PLL2; > + case NPCM7XX_CLK_PLLCONG: > + return NPCM7XX_CLOCK_PLLG; > + default: > + g_assert_not_reached(); > + } > +} > + > +static void npcm7xx_clk_update_all_plls(NPCM7xxCLKState *clk) > +{ > + int i; > + > + for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { > + npcm7xx_clk_update_pll(&clk->plls[i]); > + } > +} > + > +static void npcm7xx_clk_update_all_sels(NPCM7xxCLKState *clk) > +{ > + int i; > + > + for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { > + npcm7xx_clk_update_sel(&clk->sels[i]); > + } > +} > + > +static void npcm7xx_clk_update_all_dividers(NPCM7xxCLKState *clk) > +{ > + int i; > + > + for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { > + npcm7xx_clk_update_divider(&clk->dividers[i]); > + } > +} > + > +static void npcm7xx_clk_update_all_clocks(NPCM7xxCLKState *clk) > +{ > + clock_update_hz(clk->clkref, NPCM7XX_CLOCK_REF_HZ); > + npcm7xx_clk_update_all_plls(clk); > + npcm7xx_clk_update_all_sels(clk); > + npcm7xx_clk_update_all_dividers(clk); > +} > + > +/* Types of clock sources. */ > +typedef enum ClockSrcType { > + CLKSRC_REF, > + CLKSRC_PLL, > + CLKSRC_SEL, > + CLKSRC_DIV, > +} ClockSrcType; > + > +typedef struct PLLInitInfo { > + const char *name; > + ClockSrcType src_type; > + int src_index; > + int reg; > + const char *public_name; > +} PLLInitInfo; > + > +typedef struct SELInitInfo { > + const char *name; > + uint8_t input_size; > + ClockSrcType src_type[NPCM7XX_CLK_SEL_MAX_INPUT]; > + int src_index[NPCM7XX_CLK_SEL_MAX_INPUT]; > + int offset; > + int len; > + const char *public_name; > +} SELInitInfo; > + > +typedef struct DividerInitInfo { > + const char *name; > + ClockSrcType src_type; > + int src_index; > + uint32_t (*divide)(NPCM7xxClockDividerState *s); > + int reg; /* not used when type == CONSTANT */ > + int offset; /* not used when type == CONSTANT */ > + int len; /* not used when type == CONSTANT */ > + int divisor; /* used only when type == CONSTANT */ > + const char *public_name; > +} DividerInitInfo; > + > +static const PLLInitInfo pll_init_info_list[] = { > + [NPCM7XX_CLOCK_PLL0] = { > + .name = "pll0", > + .src_type = CLKSRC_REF, > + .reg = NPCM7XX_CLK_PLLCON0, > + }, > + [NPCM7XX_CLOCK_PLL1] = { > + .name = "pll1", > + .src_type = CLKSRC_REF, > + .reg = NPCM7XX_CLK_PLLCON1, > + }, > + [NPCM7XX_CLOCK_PLL2] = { > + .name = "pll2", > + .src_type = CLKSRC_REF, > + .reg = NPCM7XX_CLK_PLLCON2, > + }, > + [NPCM7XX_CLOCK_PLLG] = { > + .name = "pllg", > + .src_type = CLKSRC_REF, > + .reg = NPCM7XX_CLK_PLLCONG, > + }, > +}; > + > +static const SELInitInfo sel_init_info_list[] = { > + [NPCM7XX_CLOCK_PIXCKSEL] = { > + .name = "pixcksel", > + .input_size = 2, > + .src_type = {CLKSRC_PLL, CLKSRC_REF}, > + .src_index = {NPCM7XX_CLOCK_PLLG, 0}, > + .offset = 5, > + .len = 1, > + .public_name = "pixel-clock", > + }, > + [NPCM7XX_CLOCK_MCCKSEL] = { > + .name = "mccksel", > + .input_size = 4, > + .src_type = {CLKSRC_DIV, CLKSRC_REF, CLKSRC_REF, > + /*MCBPCK, shouldn't be used in normal operation*/ > + CLKSRC_REF}, > + .src_index = {NPCM7XX_CLOCK_PLL1D2, 0, 0, 0}, > + .offset = 12, > + .len = 2, > + .public_name = "mc-phy-clock", > + }, > + [NPCM7XX_CLOCK_CPUCKSEL] = { > + .name = "cpucksel", > + .input_size = 4, > + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, > + /*SYSBPCK, shouldn't be used in normal operation*/ > + CLKSRC_REF}, > + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, 0}, > + .offset = 0, > + .len = 2, > + .public_name = "system-clock", > + }, > + [NPCM7XX_CLOCK_CLKOUTSEL] = { > + .name = "clkoutsel", > + .input_size = 5, > + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, > + CLKSRC_PLL, CLKSRC_DIV}, > + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, > + NPCM7XX_CLOCK_PLLG, NPCM7XX_CLOCK_PLL2D2}, > + .offset = 18, > + .len = 3, > + .public_name = "tock", > + }, > + [NPCM7XX_CLOCK_UARTCKSEL] = { > + .name = "uartcksel", > + .input_size = 4, > + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, > + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, > + NPCM7XX_CLOCK_PLL2D2}, > + .offset = 8, > + .len = 2, > + }, > + [NPCM7XX_CLOCK_TIMCKSEL] = { > + .name = "timcksel", > + .input_size = 4, > + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, > + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, > + NPCM7XX_CLOCK_PLL2D2}, > + .offset = 14, > + .len = 2, > + }, > + [NPCM7XX_CLOCK_SDCKSEL] = { > + .name = "sdcksel", > + .input_size = 4, > + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, > + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, > + NPCM7XX_CLOCK_PLL2D2}, > + .offset = 6, > + .len = 2, > + }, > + [NPCM7XX_CLOCK_GFXMSEL] = { > + .name = "gfxmksel", > + .input_size = 2, > + .src_type = {CLKSRC_REF, CLKSRC_PLL}, > + .src_index = {0, NPCM7XX_CLOCK_PLL2}, > + .offset = 21, > + .len = 1, > + }, > + [NPCM7XX_CLOCK_SUCKSEL] = { > + .name = "sucksel", > + .input_size = 4, > + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, > + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, > + NPCM7XX_CLOCK_PLL2D2}, > + .offset = 10, > + .len = 2, > + }, > +}; > + > +static const DividerInitInfo divider_init_info_list[] = { > + [NPCM7XX_CLOCK_PLL1D2] = { > + .name = "pll1d2", > + .src_type = CLKSRC_PLL, > + .src_index = NPCM7XX_CLOCK_PLL1, > + .divide = divide_by_constant, > + .divisor = 2, > + }, > + [NPCM7XX_CLOCK_PLL2D2] = { > + .name = "pll2d2", > + .src_type = CLKSRC_PLL, > + .src_index = NPCM7XX_CLOCK_PLL2, > + .divide = divide_by_constant, > + .divisor = 2, > + }, > + [NPCM7XX_CLOCK_MC_DIVIDER] = { > + .name = "mc-divider", > + .src_type = CLKSRC_SEL, > + .src_index = NPCM7XX_CLOCK_MCCKSEL, > + .divide = divide_by_constant, > + .divisor = 2, > + .public_name = "mc-clock" > + }, > + [NPCM7XX_CLOCK_AXI_DIVIDER] = { > + .name = "axi-divider", > + .src_type = CLKSRC_SEL, > + .src_index = NPCM7XX_CLOCK_CPUCKSEL, > + .divide = shift_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV1, > + .offset = 0, > + .len = 1, > + .public_name = "clk2" > + }, > + [NPCM7XX_CLOCK_AHB_DIVIDER] = { > + .name = "ahb-divider", > + .src_type = CLKSRC_DIV, > + .src_index = NPCM7XX_CLOCK_AXI_DIVIDER, > + .divide = divide_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV1, > + .offset = 26, > + .len = 2, > + .public_name = "clk4" > + }, > + [NPCM7XX_CLOCK_AHB3_DIVIDER] = { > + .name = "ahb3-divider", > + .src_type = CLKSRC_DIV, > + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, > + .divide = divide_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV1, > + .offset = 6, > + .len = 5, > + .public_name = "ahb3-spi3-clock" > + }, > + [NPCM7XX_CLOCK_SPI0_DIVIDER] = { > + .name = "spi0-divider", > + .src_type = CLKSRC_DIV, > + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, > + .divide = divide_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV3, > + .offset = 6, > + .len = 5, > + .public_name = "spi0-clock", > + }, > + [NPCM7XX_CLOCK_SPIX_DIVIDER] = { > + .name = "spix-divider", > + .src_type = CLKSRC_DIV, > + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, > + .divide = divide_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV3, > + .offset = 1, > + .len = 5, > + .public_name = "spix-clock", > + }, > + [NPCM7XX_CLOCK_APB1_DIVIDER] = { > + .name = "apb1-divider", > + .src_type = CLKSRC_DIV, > + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, > + .divide = shift_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV2, > + .offset = 24, > + .len = 2, > + .public_name = "apb1-clock", > + }, > + [NPCM7XX_CLOCK_APB2_DIVIDER] = { > + .name = "apb2-divider", > + .src_type = CLKSRC_DIV, > + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, > + .divide = shift_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV2, > + .offset = 26, > + .len = 2, > + .public_name = "apb2-clock", > + }, > + [NPCM7XX_CLOCK_APB3_DIVIDER] = { > + .name = "apb3-divider", > + .src_type = CLKSRC_DIV, > + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, > + .divide = shift_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV2, > + .offset = 28, > + .len = 2, > + .public_name = "apb3-clock", > + }, > + [NPCM7XX_CLOCK_APB4_DIVIDER] = { > + .name = "apb4-divider", > + .src_type = CLKSRC_DIV, > + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, > + .divide = shift_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV2, > + .offset = 30, > + .len = 2, > + .public_name = "apb4-clock", > + }, > + [NPCM7XX_CLOCK_APB5_DIVIDER] = { > + .name = "apb5-divider", > + .src_type = CLKSRC_DIV, > + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, > + .divide = shift_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV2, > + .offset = 22, > + .len = 2, > + .public_name = "apb5-clock", > + }, > + [NPCM7XX_CLOCK_CLKOUT_DIVIDER] = { > + .name = "clkout-divider", > + .src_type = CLKSRC_SEL, > + .src_index = NPCM7XX_CLOCK_CLKOUTSEL, > + .divide = divide_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV2, > + .offset = 16, > + .len = 5, > + .public_name = "clkout", > + }, > + [NPCM7XX_CLOCK_UART_DIVIDER] = { > + .name = "uart-divider", > + .src_type = CLKSRC_SEL, > + .src_index = NPCM7XX_CLOCK_UARTCKSEL, > + .divide = divide_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV1, > + .offset = 16, > + .len = 5, > + .public_name = "uart-clock", > + }, > + [NPCM7XX_CLOCK_TIMER_DIVIDER] = { > + .name = "timer-divider", > + .src_type = CLKSRC_SEL, > + .src_index = NPCM7XX_CLOCK_TIMCKSEL, > + .divide = divide_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV1, > + .offset = 21, > + .len = 5, > + .public_name = "timer-clock", > + }, > + [NPCM7XX_CLOCK_ADC_DIVIDER] = { > + .name = "adc-divider", > + .src_type = CLKSRC_DIV, > + .src_index = NPCM7XX_CLOCK_TIMER_DIVIDER, > + .divide = shift_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV1, > + .offset = 28, > + .len = 3, > + .public_name = "adc-clock", > + }, > + [NPCM7XX_CLOCK_MMC_DIVIDER] = { > + .name = "mmc-divider", > + .src_type = CLKSRC_SEL, > + .src_index = NPCM7XX_CLOCK_SDCKSEL, > + .divide = divide_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV1, > + .offset = 11, > + .len = 5, > + .public_name = "mmc-clock", > + }, > + [NPCM7XX_CLOCK_SDHC_DIVIDER] = { > + .name = "sdhc-divider", > + .src_type = CLKSRC_SEL, > + .src_index = NPCM7XX_CLOCK_SDCKSEL, > + .divide = divide_by_reg_divisor_times_2, > + .reg = NPCM7XX_CLK_CLKDIV2, > + .offset = 0, > + .len = 4, > + .public_name = "sdhc-clock", > + }, > + [NPCM7XX_CLOCK_GFXM_DIVIDER] = { > + .name = "gfxm-divider", > + .src_type = CLKSRC_SEL, > + .src_index = NPCM7XX_CLOCK_GFXMSEL, > + .divide = divide_by_constant, > + .divisor = 3, > + .public_name = "gfxm-clock", > + }, > + [NPCM7XX_CLOCK_UTMI_DIVIDER] = { > + .name = "utmi-divider", > + .src_type = CLKSRC_SEL, > + .src_index = NPCM7XX_CLOCK_SUCKSEL, > + .divide = divide_by_reg_divisor, > + .reg = NPCM7XX_CLK_CLKDIV2, > + .offset = 8, > + .len = 5, > + .public_name = "utmi-clock", > + }, > +}; > + > +static void npcm7xx_clk_pll_init(Object *obj) > +{ > + NPCM7xxClockPLLState *pll = NPCM7XX_CLOCK_PLL(obj); > + > + pll->clock_in = qdev_init_clock_in(DEVICE(pll), "clock-in", > + npcm7xx_clk_update_pll, pll); > + pll->clock_out = qdev_init_clock_out(DEVICE(pll), "clock-out"); > +} > + > +static void npcm7xx_clk_sel_init(Object *obj) > +{ > + int i; > + NPCM7xxClockSELState *sel = NPCM7XX_CLOCK_SEL(obj); > + > + for (i = 0; i < NPCM7XX_CLK_SEL_MAX_INPUT; ++i) { > + sel->clock_in[i] = qdev_init_clock_in(DEVICE(sel), > + g_strdup_printf("clock-in[%d]", i), > + npcm7xx_clk_update_sel, sel); > + } > + sel->clock_out = qdev_init_clock_out(DEVICE(sel), "clock-out"); > +} > +static void npcm7xx_clk_divider_init(Object *obj) > +{ > + NPCM7xxClockDividerState *div = NPCM7XX_CLOCK_DIVIDER(obj); > + > + div->clock_in = qdev_init_clock_in(DEVICE(div), "clock-in", > + npcm7xx_clk_update_divider, div); > + div->clock_out = qdev_init_clock_out(DEVICE(div), "clock-out"); > +} > + > +static void npcm7xx_init_clock_pll(NPCM7xxClockPLLState *pll, > + NPCM7xxCLKState *clk, const PLLInitInfo *init_info) > +{ > + pll->name = init_info->name; > + pll->clk = clk; > + pll->reg = init_info->reg; > + if (init_info->public_name != NULL) { > + qdev_alias_clock(DEVICE(pll), "clock-out", DEVICE(clk), > + init_info->public_name); > + } > +} > + > +static void npcm7xx_init_clock_sel(NPCM7xxClockSELState *sel, > + NPCM7xxCLKState *clk, const SELInitInfo *init_info) > +{ > + int input_size = init_info->input_size; > + > + sel->name = init_info->name; > + sel->clk = clk; > + sel->input_size = init_info->input_size; > + g_assert(input_size <= NPCM7XX_CLK_SEL_MAX_INPUT); > + sel->offset = init_info->offset; > + sel->len = init_info->len; > + if (init_info->public_name != NULL) { > + qdev_alias_clock(DEVICE(sel), "clock-out", DEVICE(clk), > + init_info->public_name); > + } > +} > + > +static void npcm7xx_init_clock_divider(NPCM7xxClockDividerState *div, > + NPCM7xxCLKState *clk, const DividerInitInfo *init_info) > +{ > + div->name = init_info->name; > + div->clk = clk; > + > + div->divide = init_info->divide; > + if (div->divide == divide_by_constant) { > + div->divisor = init_info->divisor; > + } else { > + div->reg = init_info->reg; > + div->offset = init_info->offset; > + div->len = init_info->len; > + } > + if (init_info->public_name != NULL) { > + qdev_alias_clock(DEVICE(div), "clock-out", DEVICE(clk), > + init_info->public_name); > + } > +} > + > +static Clock *npcm7xx_get_clock(NPCM7xxCLKState *clk, ClockSrcType type, > + int index) > +{ > + switch (type) { > + case CLKSRC_REF: > + return clk->clkref; > + case CLKSRC_PLL: > + return clk->plls[index].clock_out; > + case CLKSRC_SEL: > + return clk->sels[index].clock_out; > + case CLKSRC_DIV: > + return clk->dividers[index].clock_out; > + default: > + g_assert_not_reached(); > + } > +} > + > +static void npcm7xx_connect_clocks(NPCM7xxCLKState *clk) > +{ > + int i, j; > + Clock *src; > + > + for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { > + src = npcm7xx_get_clock(clk, pll_init_info_list[i].src_type, > + pll_init_info_list[i].src_index); > + clock_set_source(clk->plls[i].clock_in, src); > + } > + for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { > + for (j = 0; j < sel_init_info_list[i].input_size; ++j) { > + src = npcm7xx_get_clock(clk, > sel_init_info_list[i].src_type[j], > + sel_init_info_list[i].src_index[j]); > + clock_set_source(clk->sels[i].clock_in[j], src); > + } > + } > + for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { > + src = npcm7xx_get_clock(clk, divider_init_info_list[i].src_type, > + divider_init_info_list[i].src_index); > + clock_set_source(clk->dividers[i].clock_in, src); > + } > +} > + > static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned > size) > { > uint32_t reg = offset / sizeof(uint32_t); > @@ -129,7 +740,7 @@ static uint64_t npcm7xx_clk_read(void *opaque, hwaddr > offset, unsigned size) > * > * The 4 LSBs are always zero: (1e9 / 640) << 4 = 25000000. > */ > - value = (((now_ns - s->ref_ns) / 640) << 4) % > NPCM7XX_TIMER_REF_HZ; > + value = (((now_ns - s->ref_ns) / 640) << 4) % > NPCM7XX_CLOCK_REF_HZ; > break; > > default: > @@ -183,6 +794,20 @@ static void npcm7xx_clk_write(void *opaque, hwaddr > offset, > value |= (value & PLLCON_LOKS); > } > } > + /* Only update PLL when it is locked. */ > + if (value & PLLCON_LOKI) { > + npcm7xx_clk_update_pll(&s->plls[find_pll_by_reg(reg)]); > + } > + break; > + > + case NPCM7XX_CLK_CLKSEL: > + npcm7xx_clk_update_all_sels(s); > + break; > + > + case NPCM7XX_CLK_CLKDIV1: > + case NPCM7XX_CLK_CLKDIV2: > + case NPCM7XX_CLK_CLKDIV3: > + npcm7xx_clk_update_all_dividers(s); > break; > > case NPCM7XX_CLK_CNTR25M: > @@ -234,6 +859,7 @@ static void npcm7xx_clk_enter_reset(Object *obj, > ResetType type) > case RESET_TYPE_COLD: > memcpy(s->regs, cold_reset_values, sizeof(cold_reset_values)); > s->ref_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); > + npcm7xx_clk_update_all_clocks(s); > return; > } > > @@ -245,6 +871,42 @@ static void npcm7xx_clk_enter_reset(Object *obj, > ResetType type) > __func__, type); > } > > +static void npcm7xx_clk_init_clock_hierarchy(NPCM7xxCLKState *s) > +{ > + int i; > + > + s->clkref = qdev_init_clock_in(DEVICE(s), "clkref", NULL, NULL); > + > + /* First pass: init all converter modules */ > + QEMU_BUILD_BUG_ON(ARRAY_SIZE(pll_init_info_list) != > NPCM7XX_CLOCK_NR_PLLS); > + QEMU_BUILD_BUG_ON(ARRAY_SIZE(sel_init_info_list) != > NPCM7XX_CLOCK_NR_SELS); > + QEMU_BUILD_BUG_ON(ARRAY_SIZE(divider_init_info_list) > + != NPCM7XX_CLOCK_NR_DIVIDERS); > + for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { > + object_initialize_child(OBJECT(s), pll_init_info_list[i].name, > + &s->plls[i], TYPE_NPCM7XX_CLOCK_PLL); > + npcm7xx_init_clock_pll(&s->plls[i], s, > + &pll_init_info_list[i]); > + } > + for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { > + object_initialize_child(OBJECT(s), sel_init_info_list[i].name, > + &s->sels[i], TYPE_NPCM7XX_CLOCK_SEL); > + npcm7xx_init_clock_sel(&s->sels[i], s, > + &sel_init_info_list[i]); > + } > + for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { > + object_initialize_child(OBJECT(s), divider_init_info_list[i].name, > + &s->dividers[i], TYPE_NPCM7XX_CLOCK_DIVIDER); > + npcm7xx_init_clock_divider(&s->dividers[i], s, > + ÷r_init_info_list[i]); > + } > + > + /* Second pass: connect converter modules */ > + npcm7xx_connect_clocks(s); > + > + clock_update_hz(s->clkref, NPCM7XX_CLOCK_REF_HZ); > +} > + > static void npcm7xx_clk_init(Object *obj) > { > NPCM7xxCLKState *s = NPCM7XX_CLK(obj); > @@ -252,21 +914,114 @@ static void npcm7xx_clk_init(Object *obj) > memory_region_init_io(&s->iomem, obj, &npcm7xx_clk_ops, s, > TYPE_NPCM7XX_CLK, 4 * KiB); > sysbus_init_mmio(&s->parent, &s->iomem); > +} > + > +static int npcm7xx_clk_post_load(void *opaque, int version_id) > +{ > + if (version_id >= 1) { > + NPCM7xxCLKState *clk = opaque; > + > + npcm7xx_clk_update_all_clocks(clk); > + } > + > + return 0; > +} > + > +static void npcm7xx_clk_realize(DeviceState *dev, Error **errp) > +{ > + int i; > + NPCM7xxCLKState *s = NPCM7XX_CLK(dev); > + > qdev_init_gpio_in_named(DEVICE(s), npcm7xx_clk_perform_watchdog_reset, > NPCM7XX_WATCHDOG_RESET_GPIO_IN, NPCM7XX_NR_WATCHDOGS); > + npcm7xx_clk_init_clock_hierarchy(s); > + > + /* Realize child devices */ > + for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { > + if (!qdev_realize(DEVICE(&s->plls[i]), NULL, errp)) { > + return; > + } > + } > + for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { > + if (!qdev_realize(DEVICE(&s->sels[i]), NULL, errp)) { > + return; > + } > + } > + for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { > + if (!qdev_realize(DEVICE(&s->dividers[i]), NULL, errp)) { > + return; > + } > + } > } > > -static const VMStateDescription vmstate_npcm7xx_clk = { > - .name = "npcm7xx-clk", > +static const VMStateDescription vmstate_npcm7xx_clk_pll = { > + .name = "npcm7xx-clock-pll", > + .version_id = 0, > + .minimum_version_id = 0, > + .fields = (VMStateField[]) { > + VMSTATE_CLOCK(clock_in, NPCM7xxClockPLLState), > + VMSTATE_END_OF_LIST(), > + }, > +}; > + > +static const VMStateDescription vmstate_npcm7xx_clk_sel = { > + .name = "npcm7xx-clock-sel", > + .version_id = 0, > + .minimum_version_id = 0, > + .fields = (VMStateField[]) { > + VMSTATE_ARRAY_OF_POINTER_TO_STRUCT(clock_in, NPCM7xxClockSELState, > + NPCM7XX_CLK_SEL_MAX_INPUT, 0, vmstate_clock, Clock), > + VMSTATE_END_OF_LIST(), > + }, > +}; > + > +static const VMStateDescription vmstate_npcm7xx_clk_divider = { > + .name = "npcm7xx-clock-divider", > .version_id = 0, > .minimum_version_id = 0, > + .fields = (VMStateField[]) { > + VMSTATE_CLOCK(clock_in, NPCM7xxClockDividerState), > + VMSTATE_END_OF_LIST(), > + }, > +}; > + > +static const VMStateDescription vmstate_npcm7xx_clk = { > + .name = "npcm7xx-clk", > + .version_id = 1, > + .minimum_version_id = 1, > + .post_load = npcm7xx_clk_post_load, > .fields = (VMStateField[]) { > VMSTATE_UINT32_ARRAY(regs, NPCM7xxCLKState, NPCM7XX_CLK_NR_REGS), > VMSTATE_INT64(ref_ns, NPCM7xxCLKState), > + VMSTATE_CLOCK(clkref, NPCM7xxCLKState), > VMSTATE_END_OF_LIST(), > }, > }; > > +static void npcm7xx_clk_pll_class_init(ObjectClass *klass, void *data) > +{ > + DeviceClass *dc = DEVICE_CLASS(klass); > + > + dc->desc = "NPCM7xx Clock PLL Module"; > + dc->vmsd = &vmstate_npcm7xx_clk_pll; > +} > + > +static void npcm7xx_clk_sel_class_init(ObjectClass *klass, void *data) > +{ > + DeviceClass *dc = DEVICE_CLASS(klass); > + > + dc->desc = "NPCM7xx Clock SEL Module"; > + dc->vmsd = &vmstate_npcm7xx_clk_sel; > +} > + > +static void npcm7xx_clk_divider_class_init(ObjectClass *klass, void *data) > +{ > + DeviceClass *dc = DEVICE_CLASS(klass); > + > + dc->desc = "NPCM7xx Clock Divider Module"; > + dc->vmsd = &vmstate_npcm7xx_clk_divider; > +} > + > static void npcm7xx_clk_class_init(ObjectClass *klass, void *data) > { > ResettableClass *rc = RESETTABLE_CLASS(klass); > @@ -276,9 +1031,34 @@ static void npcm7xx_clk_class_init(ObjectClass > *klass, void *data) > > dc->desc = "NPCM7xx Clock Control Registers"; > dc->vmsd = &vmstate_npcm7xx_clk; > + dc->realize = npcm7xx_clk_realize; > rc->phases.enter = npcm7xx_clk_enter_reset; > } > > +static const TypeInfo npcm7xx_clk_pll_info = { > + .name = TYPE_NPCM7XX_CLOCK_PLL, > + .parent = TYPE_DEVICE, > + .instance_size = sizeof(NPCM7xxClockPLLState), > + .instance_init = npcm7xx_clk_pll_init, > + .class_init = npcm7xx_clk_pll_class_init, > +}; > + > +static const TypeInfo npcm7xx_clk_sel_info = { > + .name = TYPE_NPCM7XX_CLOCK_SEL, > + .parent = TYPE_DEVICE, > + .instance_size = sizeof(NPCM7xxClockSELState), > + .instance_init = npcm7xx_clk_sel_init, > + .class_init = npcm7xx_clk_sel_class_init, > +}; > + > +static const TypeInfo npcm7xx_clk_divider_info = { > + .name = TYPE_NPCM7XX_CLOCK_DIVIDER, > + .parent = TYPE_DEVICE, > + .instance_size = sizeof(NPCM7xxClockDividerState), > + .instance_init = npcm7xx_clk_divider_init, > + .class_init = npcm7xx_clk_divider_class_init, > +}; > + > static const TypeInfo npcm7xx_clk_info = { > .name = TYPE_NPCM7XX_CLK, > .parent = TYPE_SYS_BUS_DEVICE, > @@ -289,6 +1069,9 @@ static const TypeInfo npcm7xx_clk_info = { > > static void npcm7xx_clk_register_type(void) > { > + type_register_static(&npcm7xx_clk_pll_info); > + type_register_static(&npcm7xx_clk_sel_info); > + type_register_static(&npcm7xx_clk_divider_info); > type_register_static(&npcm7xx_clk_info); > } > type_init(npcm7xx_clk_register_type); > diff --git a/include/hw/misc/npcm7xx_clk.h b/include/hw/misc/npcm7xx_clk.h > index 2338fbbdb5..f641f95f3e 100644 > --- a/include/hw/misc/npcm7xx_clk.h > +++ b/include/hw/misc/npcm7xx_clk.h > @@ -17,6 +17,7 @@ > #define NPCM7XX_CLK_H > > #include "exec/memory.h" > +#include "hw/clock.h" > #include "hw/sysbus.h" > > /* > @@ -33,16 +34,151 @@ > > #define NPCM7XX_WATCHDOG_RESET_GPIO_IN > "npcm7xx-clk-watchdog-reset-gpio-in" > > -typedef struct NPCM7xxCLKState { > +/* Maximum amount of clock inputs in a SEL module. */ > +#define NPCM7XX_CLK_SEL_MAX_INPUT 5 > + > +/* PLLs in CLK module. */ > +typedef enum NPCM7xxClockPLL { > + NPCM7XX_CLOCK_PLL0, > + NPCM7XX_CLOCK_PLL1, > + NPCM7XX_CLOCK_PLL2, > + NPCM7XX_CLOCK_PLLG, > + NPCM7XX_CLOCK_NR_PLLS, > +} NPCM7xxClockPLL; > + > +/* SEL/MUX in CLK module. */ > +typedef enum NPCM7xxClockSEL { > + NPCM7XX_CLOCK_PIXCKSEL, > + NPCM7XX_CLOCK_MCCKSEL, > + NPCM7XX_CLOCK_CPUCKSEL, > + NPCM7XX_CLOCK_CLKOUTSEL, > + NPCM7XX_CLOCK_UARTCKSEL, > + NPCM7XX_CLOCK_TIMCKSEL, > + NPCM7XX_CLOCK_SDCKSEL, > + NPCM7XX_CLOCK_GFXMSEL, > + NPCM7XX_CLOCK_SUCKSEL, > + NPCM7XX_CLOCK_NR_SELS, > +} NPCM7xxClockSEL; > + > +/* Dividers in CLK module. */ > +typedef enum NPCM7xxClockDivider { > + NPCM7XX_CLOCK_PLL1D2, /* PLL1/2 */ > + NPCM7XX_CLOCK_PLL2D2, /* PLL2/2 */ > + NPCM7XX_CLOCK_MC_DIVIDER, > + NPCM7XX_CLOCK_AXI_DIVIDER, > + NPCM7XX_CLOCK_AHB_DIVIDER, > + NPCM7XX_CLOCK_AHB3_DIVIDER, > + NPCM7XX_CLOCK_SPI0_DIVIDER, > + NPCM7XX_CLOCK_SPIX_DIVIDER, > + NPCM7XX_CLOCK_APB1_DIVIDER, > + NPCM7XX_CLOCK_APB2_DIVIDER, > + NPCM7XX_CLOCK_APB3_DIVIDER, > + NPCM7XX_CLOCK_APB4_DIVIDER, > + NPCM7XX_CLOCK_APB5_DIVIDER, > + NPCM7XX_CLOCK_CLKOUT_DIVIDER, > + NPCM7XX_CLOCK_UART_DIVIDER, > + NPCM7XX_CLOCK_TIMER_DIVIDER, > + NPCM7XX_CLOCK_ADC_DIVIDER, > + NPCM7XX_CLOCK_MMC_DIVIDER, > + NPCM7XX_CLOCK_SDHC_DIVIDER, > + NPCM7XX_CLOCK_GFXM_DIVIDER, /* divide by 3 */ > + NPCM7XX_CLOCK_UTMI_DIVIDER, > + NPCM7XX_CLOCK_NR_DIVIDERS, > +} NPCM7xxClockConverter; > + > +typedef struct NPCM7xxCLKState NPCM7xxCLKState; > + > +/** > + * struct NPCM7xxClockPLLState - A PLL module in CLK module. > + * @name: The name of the module. > + * @clk: The CLK module that owns this module. > + * @clock_in: The input clock of this module. > + * @clock_out: The output clock of this module. > + * @reg: The control registers for this PLL module. > + */ > +typedef struct NPCM7xxClockPLLState { > + DeviceState parent; > + > + const char *name; > + NPCM7xxCLKState *clk; > + Clock *clock_in; > + Clock *clock_out; > + > + int reg; > +} NPCM7xxClockPLLState; > + > +/** > + * struct NPCM7xxClockSELState - A SEL module in CLK module. > + * @name: The name of the module. > + * @clk: The CLK module that owns this module. > + * @input_size: The size of inputs of this module. > + * @clock_in: The input clocks of this module. > + * @clock_out: The output clocks of this module. > + * @offset: The offset of this module in the control register. > + * @len: The length of this module in the control register. > + */ > +typedef struct NPCM7xxClockSELState { > + DeviceState parent; > + > + const char *name; > + NPCM7xxCLKState *clk; > + uint8_t input_size; > + Clock *clock_in[NPCM7XX_CLK_SEL_MAX_INPUT]; > + Clock *clock_out; > + > + int offset; > + int len; > +} NPCM7xxClockSELState; > + > +/** > + * struct NPCM7xxClockDividerState - A Divider module in CLK module. > + * @name: The name of the module. > + * @clk: The CLK module that owns this module. > + * @clock_in: The input clock of this module. > + * @clock_out: The output clock of this module. > + * @divide: The function the divider uses to divide the input. > + * @reg: The index of the control register that contains the divisor. > + * @offset: The offset of the divisor in the control register. > + * @len: The length of the divisor in the control register. > + * @divisor: The divisor for a constant divisor > + */ > +typedef struct NPCM7xxClockDividerState { > + DeviceState parent; > + > + const char *name; > + NPCM7xxCLKState *clk; > + Clock *clock_in; > + Clock *clock_out; > + > + uint32_t (*divide)(struct NPCM7xxClockDividerState *s); > + union { > + struct { > + int reg; > + int offset; > + int len; > + }; > + int divisor; > + }; > +} NPCM7xxClockDividerState; > + > +struct NPCM7xxCLKState { > SysBusDevice parent; > > MemoryRegion iomem; > > + /* Clock converters */ > + NPCM7xxClockPLLState plls[NPCM7XX_CLOCK_NR_PLLS]; > + NPCM7xxClockSELState sels[NPCM7XX_CLOCK_NR_SELS]; > + NPCM7xxClockDividerState dividers[NPCM7XX_CLOCK_NR_DIVIDERS]; > + > uint32_t regs[NPCM7XX_CLK_NR_REGS]; > > /* Time reference for SECCNT and CNTR25M, initialized by power on > reset */ > int64_t ref_ns; > -} NPCM7xxCLKState; > + > + /* The incoming reference clock. */ > + Clock *clkref; > +}; > > #define TYPE_NPCM7XX_CLK "npcm7xx-clk" > #define NPCM7XX_CLK(obj) OBJECT_CHECK(NPCM7xxCLKState, (obj), > TYPE_NPCM7XX_CLK) > -- > 2.29.2.684.gfbc64c5ab5-goog > >
diff --git a/hw/misc/npcm7xx_clk.c b/hw/misc/npcm7xx_clk.c index 6732437fe2..48bc9bdda5 100644 --- a/hw/misc/npcm7xx_clk.c +++ b/hw/misc/npcm7xx_clk.c @@ -18,6 +18,7 @@ #include "hw/misc/npcm7xx_clk.h" #include "hw/timer/npcm7xx_timer.h" +#include "hw/qdev-clock.h" #include "migration/vmstate.h" #include "qemu/error-report.h" #include "qemu/log.h" @@ -27,9 +28,22 @@ #include "trace.h" #include "sysemu/watchdog.h" +/* + * The reference clock hz, and the SECCNT and CNTR25M registers in this module, + * is always 25 MHz. + */ +#define NPCM7XX_CLOCK_REF_HZ (25000000) + +/* Register Field Definitions */ +#define NPCM7XX_CLK_WDRCR_CA9C BIT(0) /* Cortex A9 Cores */ + #define PLLCON_LOKI BIT(31) #define PLLCON_LOKS BIT(30) #define PLLCON_PWDEN BIT(12) +#define PLLCON_FBDV(con) extract32((con), 16, 12) +#define PLLCON_OTDV2(con) extract32((con), 13, 3) +#define PLLCON_OTDV1(con) extract32((con), 8, 3) +#define PLLCON_INDV(con) extract32((con), 0, 6) enum NPCM7xxCLKRegisters { NPCM7XX_CLK_CLKEN1, @@ -89,12 +103,609 @@ static const uint32_t cold_reset_values[NPCM7XX_CLK_NR_REGS] = { [NPCM7XX_CLK_AHBCKFI] = 0x000000c8, }; -/* Register Field Definitions */ -#define NPCM7XX_CLK_WDRCR_CA9C BIT(0) /* Cortex A9 Cores */ - /* The number of watchdogs that can trigger a reset. */ #define NPCM7XX_NR_WATCHDOGS (3) +/* Clock converter functions */ + +#define TYPE_NPCM7XX_CLOCK_PLL "npcm7xx-clock-pll" +#define NPCM7XX_CLOCK_PLL(obj) OBJECT_CHECK(NPCM7xxClockPLLState, \ + (obj), TYPE_NPCM7XX_CLOCK_PLL) +#define TYPE_NPCM7XX_CLOCK_SEL "npcm7xx-clock-sel" +#define NPCM7XX_CLOCK_SEL(obj) OBJECT_CHECK(NPCM7xxClockSELState, \ + (obj), TYPE_NPCM7XX_CLOCK_SEL) +#define TYPE_NPCM7XX_CLOCK_DIVIDER "npcm7xx-clock-divider" +#define NPCM7XX_CLOCK_DIVIDER(obj) OBJECT_CHECK(NPCM7xxClockDividerState, \ + (obj), TYPE_NPCM7XX_CLOCK_DIVIDER) + +static void npcm7xx_clk_update_pll(void *opaque) +{ + NPCM7xxClockPLLState *s = opaque; + uint32_t con = s->clk->regs[s->reg]; + uint64_t freq; + + /* The PLL is grounded if it is not locked yet. */ + if (con & PLLCON_LOKI) { + freq = clock_get_hz(s->clock_in); + freq *= PLLCON_FBDV(con); + freq /= PLLCON_INDV(con) * PLLCON_OTDV1(con) * PLLCON_OTDV2(con); + } else { + freq = 0; + } + + clock_update_hz(s->clock_out, freq); +} + +static void npcm7xx_clk_update_sel(void *opaque) +{ + NPCM7xxClockSELState *s = opaque; + uint32_t index = extract32(s->clk->regs[NPCM7XX_CLK_CLKSEL], s->offset, + s->len); + + if (index >= s->input_size) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: SEL index: %u out of range\n", + __func__, index); + index = 0; + } + clock_update_hz(s->clock_out, clock_get_hz(s->clock_in[index])); +} + +static void npcm7xx_clk_update_divider(void *opaque) +{ + NPCM7xxClockDividerState *s = opaque; + uint32_t freq; + + freq = s->divide(s); + clock_update_hz(s->clock_out, freq); +} + +static uint32_t divide_by_constant(NPCM7xxClockDividerState *s) +{ + return clock_get_hz(s->clock_in) / s->divisor; +} + +static uint32_t divide_by_reg_divisor(NPCM7xxClockDividerState *s) +{ + return clock_get_hz(s->clock_in) / + (extract32(s->clk->regs[s->reg], s->offset, s->len) + 1); +} + +static uint32_t divide_by_reg_divisor_times_2(NPCM7xxClockDividerState *s) +{ + return divide_by_reg_divisor(s) / 2; +} + +static uint32_t shift_by_reg_divisor(NPCM7xxClockDividerState *s) +{ + return clock_get_hz(s->clock_in) >> + extract32(s->clk->regs[s->reg], s->offset, s->len); +} + +static NPCM7xxClockPLL find_pll_by_reg(enum NPCM7xxCLKRegisters reg) +{ + switch (reg) { + case NPCM7XX_CLK_PLLCON0: + return NPCM7XX_CLOCK_PLL0; + case NPCM7XX_CLK_PLLCON1: + return NPCM7XX_CLOCK_PLL1; + case NPCM7XX_CLK_PLLCON2: + return NPCM7XX_CLOCK_PLL2; + case NPCM7XX_CLK_PLLCONG: + return NPCM7XX_CLOCK_PLLG; + default: + g_assert_not_reached(); + } +} + +static void npcm7xx_clk_update_all_plls(NPCM7xxCLKState *clk) +{ + int i; + + for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { + npcm7xx_clk_update_pll(&clk->plls[i]); + } +} + +static void npcm7xx_clk_update_all_sels(NPCM7xxCLKState *clk) +{ + int i; + + for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { + npcm7xx_clk_update_sel(&clk->sels[i]); + } +} + +static void npcm7xx_clk_update_all_dividers(NPCM7xxCLKState *clk) +{ + int i; + + for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { + npcm7xx_clk_update_divider(&clk->dividers[i]); + } +} + +static void npcm7xx_clk_update_all_clocks(NPCM7xxCLKState *clk) +{ + clock_update_hz(clk->clkref, NPCM7XX_CLOCK_REF_HZ); + npcm7xx_clk_update_all_plls(clk); + npcm7xx_clk_update_all_sels(clk); + npcm7xx_clk_update_all_dividers(clk); +} + +/* Types of clock sources. */ +typedef enum ClockSrcType { + CLKSRC_REF, + CLKSRC_PLL, + CLKSRC_SEL, + CLKSRC_DIV, +} ClockSrcType; + +typedef struct PLLInitInfo { + const char *name; + ClockSrcType src_type; + int src_index; + int reg; + const char *public_name; +} PLLInitInfo; + +typedef struct SELInitInfo { + const char *name; + uint8_t input_size; + ClockSrcType src_type[NPCM7XX_CLK_SEL_MAX_INPUT]; + int src_index[NPCM7XX_CLK_SEL_MAX_INPUT]; + int offset; + int len; + const char *public_name; +} SELInitInfo; + +typedef struct DividerInitInfo { + const char *name; + ClockSrcType src_type; + int src_index; + uint32_t (*divide)(NPCM7xxClockDividerState *s); + int reg; /* not used when type == CONSTANT */ + int offset; /* not used when type == CONSTANT */ + int len; /* not used when type == CONSTANT */ + int divisor; /* used only when type == CONSTANT */ + const char *public_name; +} DividerInitInfo; + +static const PLLInitInfo pll_init_info_list[] = { + [NPCM7XX_CLOCK_PLL0] = { + .name = "pll0", + .src_type = CLKSRC_REF, + .reg = NPCM7XX_CLK_PLLCON0, + }, + [NPCM7XX_CLOCK_PLL1] = { + .name = "pll1", + .src_type = CLKSRC_REF, + .reg = NPCM7XX_CLK_PLLCON1, + }, + [NPCM7XX_CLOCK_PLL2] = { + .name = "pll2", + .src_type = CLKSRC_REF, + .reg = NPCM7XX_CLK_PLLCON2, + }, + [NPCM7XX_CLOCK_PLLG] = { + .name = "pllg", + .src_type = CLKSRC_REF, + .reg = NPCM7XX_CLK_PLLCONG, + }, +}; + +static const SELInitInfo sel_init_info_list[] = { + [NPCM7XX_CLOCK_PIXCKSEL] = { + .name = "pixcksel", + .input_size = 2, + .src_type = {CLKSRC_PLL, CLKSRC_REF}, + .src_index = {NPCM7XX_CLOCK_PLLG, 0}, + .offset = 5, + .len = 1, + .public_name = "pixel-clock", + }, + [NPCM7XX_CLOCK_MCCKSEL] = { + .name = "mccksel", + .input_size = 4, + .src_type = {CLKSRC_DIV, CLKSRC_REF, CLKSRC_REF, + /*MCBPCK, shouldn't be used in normal operation*/ + CLKSRC_REF}, + .src_index = {NPCM7XX_CLOCK_PLL1D2, 0, 0, 0}, + .offset = 12, + .len = 2, + .public_name = "mc-phy-clock", + }, + [NPCM7XX_CLOCK_CPUCKSEL] = { + .name = "cpucksel", + .input_size = 4, + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, + /*SYSBPCK, shouldn't be used in normal operation*/ + CLKSRC_REF}, + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, 0}, + .offset = 0, + .len = 2, + .public_name = "system-clock", + }, + [NPCM7XX_CLOCK_CLKOUTSEL] = { + .name = "clkoutsel", + .input_size = 5, + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, + CLKSRC_PLL, CLKSRC_DIV}, + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, + NPCM7XX_CLOCK_PLLG, NPCM7XX_CLOCK_PLL2D2}, + .offset = 18, + .len = 3, + .public_name = "tock", + }, + [NPCM7XX_CLOCK_UARTCKSEL] = { + .name = "uartcksel", + .input_size = 4, + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, + NPCM7XX_CLOCK_PLL2D2}, + .offset = 8, + .len = 2, + }, + [NPCM7XX_CLOCK_TIMCKSEL] = { + .name = "timcksel", + .input_size = 4, + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, + NPCM7XX_CLOCK_PLL2D2}, + .offset = 14, + .len = 2, + }, + [NPCM7XX_CLOCK_SDCKSEL] = { + .name = "sdcksel", + .input_size = 4, + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, + NPCM7XX_CLOCK_PLL2D2}, + .offset = 6, + .len = 2, + }, + [NPCM7XX_CLOCK_GFXMSEL] = { + .name = "gfxmksel", + .input_size = 2, + .src_type = {CLKSRC_REF, CLKSRC_PLL}, + .src_index = {0, NPCM7XX_CLOCK_PLL2}, + .offset = 21, + .len = 1, + }, + [NPCM7XX_CLOCK_SUCKSEL] = { + .name = "sucksel", + .input_size = 4, + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, + NPCM7XX_CLOCK_PLL2D2}, + .offset = 10, + .len = 2, + }, +}; + +static const DividerInitInfo divider_init_info_list[] = { + [NPCM7XX_CLOCK_PLL1D2] = { + .name = "pll1d2", + .src_type = CLKSRC_PLL, + .src_index = NPCM7XX_CLOCK_PLL1, + .divide = divide_by_constant, + .divisor = 2, + }, + [NPCM7XX_CLOCK_PLL2D2] = { + .name = "pll2d2", + .src_type = CLKSRC_PLL, + .src_index = NPCM7XX_CLOCK_PLL2, + .divide = divide_by_constant, + .divisor = 2, + }, + [NPCM7XX_CLOCK_MC_DIVIDER] = { + .name = "mc-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_MCCKSEL, + .divide = divide_by_constant, + .divisor = 2, + .public_name = "mc-clock" + }, + [NPCM7XX_CLOCK_AXI_DIVIDER] = { + .name = "axi-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_CPUCKSEL, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 0, + .len = 1, + .public_name = "clk2" + }, + [NPCM7XX_CLOCK_AHB_DIVIDER] = { + .name = "ahb-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AXI_DIVIDER, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 26, + .len = 2, + .public_name = "clk4" + }, + [NPCM7XX_CLOCK_AHB3_DIVIDER] = { + .name = "ahb3-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 6, + .len = 5, + .public_name = "ahb3-spi3-clock" + }, + [NPCM7XX_CLOCK_SPI0_DIVIDER] = { + .name = "spi0-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV3, + .offset = 6, + .len = 5, + .public_name = "spi0-clock", + }, + [NPCM7XX_CLOCK_SPIX_DIVIDER] = { + .name = "spix-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV3, + .offset = 1, + .len = 5, + .public_name = "spix-clock", + }, + [NPCM7XX_CLOCK_APB1_DIVIDER] = { + .name = "apb1-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 24, + .len = 2, + .public_name = "apb1-clock", + }, + [NPCM7XX_CLOCK_APB2_DIVIDER] = { + .name = "apb2-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 26, + .len = 2, + .public_name = "apb2-clock", + }, + [NPCM7XX_CLOCK_APB3_DIVIDER] = { + .name = "apb3-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 28, + .len = 2, + .public_name = "apb3-clock", + }, + [NPCM7XX_CLOCK_APB4_DIVIDER] = { + .name = "apb4-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 30, + .len = 2, + .public_name = "apb4-clock", + }, + [NPCM7XX_CLOCK_APB5_DIVIDER] = { + .name = "apb5-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 22, + .len = 2, + .public_name = "apb5-clock", + }, + [NPCM7XX_CLOCK_CLKOUT_DIVIDER] = { + .name = "clkout-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_CLKOUTSEL, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 16, + .len = 5, + .public_name = "clkout", + }, + [NPCM7XX_CLOCK_UART_DIVIDER] = { + .name = "uart-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_UARTCKSEL, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 16, + .len = 5, + .public_name = "uart-clock", + }, + [NPCM7XX_CLOCK_TIMER_DIVIDER] = { + .name = "timer-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_TIMCKSEL, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 21, + .len = 5, + .public_name = "timer-clock", + }, + [NPCM7XX_CLOCK_ADC_DIVIDER] = { + .name = "adc-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_TIMER_DIVIDER, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 28, + .len = 3, + .public_name = "adc-clock", + }, + [NPCM7XX_CLOCK_MMC_DIVIDER] = { + .name = "mmc-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_SDCKSEL, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 11, + .len = 5, + .public_name = "mmc-clock", + }, + [NPCM7XX_CLOCK_SDHC_DIVIDER] = { + .name = "sdhc-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_SDCKSEL, + .divide = divide_by_reg_divisor_times_2, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 0, + .len = 4, + .public_name = "sdhc-clock", + }, + [NPCM7XX_CLOCK_GFXM_DIVIDER] = { + .name = "gfxm-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_GFXMSEL, + .divide = divide_by_constant, + .divisor = 3, + .public_name = "gfxm-clock", + }, + [NPCM7XX_CLOCK_UTMI_DIVIDER] = { + .name = "utmi-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_SUCKSEL, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 8, + .len = 5, + .public_name = "utmi-clock", + }, +}; + +static void npcm7xx_clk_pll_init(Object *obj) +{ + NPCM7xxClockPLLState *pll = NPCM7XX_CLOCK_PLL(obj); + + pll->clock_in = qdev_init_clock_in(DEVICE(pll), "clock-in", + npcm7xx_clk_update_pll, pll); + pll->clock_out = qdev_init_clock_out(DEVICE(pll), "clock-out"); +} + +static void npcm7xx_clk_sel_init(Object *obj) +{ + int i; + NPCM7xxClockSELState *sel = NPCM7XX_CLOCK_SEL(obj); + + for (i = 0; i < NPCM7XX_CLK_SEL_MAX_INPUT; ++i) { + sel->clock_in[i] = qdev_init_clock_in(DEVICE(sel), + g_strdup_printf("clock-in[%d]", i), + npcm7xx_clk_update_sel, sel); + } + sel->clock_out = qdev_init_clock_out(DEVICE(sel), "clock-out"); +} +static void npcm7xx_clk_divider_init(Object *obj) +{ + NPCM7xxClockDividerState *div = NPCM7XX_CLOCK_DIVIDER(obj); + + div->clock_in = qdev_init_clock_in(DEVICE(div), "clock-in", + npcm7xx_clk_update_divider, div); + div->clock_out = qdev_init_clock_out(DEVICE(div), "clock-out"); +} + +static void npcm7xx_init_clock_pll(NPCM7xxClockPLLState *pll, + NPCM7xxCLKState *clk, const PLLInitInfo *init_info) +{ + pll->name = init_info->name; + pll->clk = clk; + pll->reg = init_info->reg; + if (init_info->public_name != NULL) { + qdev_alias_clock(DEVICE(pll), "clock-out", DEVICE(clk), + init_info->public_name); + } +} + +static void npcm7xx_init_clock_sel(NPCM7xxClockSELState *sel, + NPCM7xxCLKState *clk, const SELInitInfo *init_info) +{ + int input_size = init_info->input_size; + + sel->name = init_info->name; + sel->clk = clk; + sel->input_size = init_info->input_size; + g_assert(input_size <= NPCM7XX_CLK_SEL_MAX_INPUT); + sel->offset = init_info->offset; + sel->len = init_info->len; + if (init_info->public_name != NULL) { + qdev_alias_clock(DEVICE(sel), "clock-out", DEVICE(clk), + init_info->public_name); + } +} + +static void npcm7xx_init_clock_divider(NPCM7xxClockDividerState *div, + NPCM7xxCLKState *clk, const DividerInitInfo *init_info) +{ + div->name = init_info->name; + div->clk = clk; + + div->divide = init_info->divide; + if (div->divide == divide_by_constant) { + div->divisor = init_info->divisor; + } else { + div->reg = init_info->reg; + div->offset = init_info->offset; + div->len = init_info->len; + } + if (init_info->public_name != NULL) { + qdev_alias_clock(DEVICE(div), "clock-out", DEVICE(clk), + init_info->public_name); + } +} + +static Clock *npcm7xx_get_clock(NPCM7xxCLKState *clk, ClockSrcType type, + int index) +{ + switch (type) { + case CLKSRC_REF: + return clk->clkref; + case CLKSRC_PLL: + return clk->plls[index].clock_out; + case CLKSRC_SEL: + return clk->sels[index].clock_out; + case CLKSRC_DIV: + return clk->dividers[index].clock_out; + default: + g_assert_not_reached(); + } +} + +static void npcm7xx_connect_clocks(NPCM7xxCLKState *clk) +{ + int i, j; + Clock *src; + + for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { + src = npcm7xx_get_clock(clk, pll_init_info_list[i].src_type, + pll_init_info_list[i].src_index); + clock_set_source(clk->plls[i].clock_in, src); + } + for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { + for (j = 0; j < sel_init_info_list[i].input_size; ++j) { + src = npcm7xx_get_clock(clk, sel_init_info_list[i].src_type[j], + sel_init_info_list[i].src_index[j]); + clock_set_source(clk->sels[i].clock_in[j], src); + } + } + for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { + src = npcm7xx_get_clock(clk, divider_init_info_list[i].src_type, + divider_init_info_list[i].src_index); + clock_set_source(clk->dividers[i].clock_in, src); + } +} + static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size) { uint32_t reg = offset / sizeof(uint32_t); @@ -129,7 +740,7 @@ static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size) * * The 4 LSBs are always zero: (1e9 / 640) << 4 = 25000000. */ - value = (((now_ns - s->ref_ns) / 640) << 4) % NPCM7XX_TIMER_REF_HZ; + value = (((now_ns - s->ref_ns) / 640) << 4) % NPCM7XX_CLOCK_REF_HZ; break; default: @@ -183,6 +794,20 @@ static void npcm7xx_clk_write(void *opaque, hwaddr offset, value |= (value & PLLCON_LOKS); } } + /* Only update PLL when it is locked. */ + if (value & PLLCON_LOKI) { + npcm7xx_clk_update_pll(&s->plls[find_pll_by_reg(reg)]); + } + break; + + case NPCM7XX_CLK_CLKSEL: + npcm7xx_clk_update_all_sels(s); + break; + + case NPCM7XX_CLK_CLKDIV1: + case NPCM7XX_CLK_CLKDIV2: + case NPCM7XX_CLK_CLKDIV3: + npcm7xx_clk_update_all_dividers(s); break; case NPCM7XX_CLK_CNTR25M: @@ -234,6 +859,7 @@ static void npcm7xx_clk_enter_reset(Object *obj, ResetType type) case RESET_TYPE_COLD: memcpy(s->regs, cold_reset_values, sizeof(cold_reset_values)); s->ref_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + npcm7xx_clk_update_all_clocks(s); return; } @@ -245,6 +871,42 @@ static void npcm7xx_clk_enter_reset(Object *obj, ResetType type) __func__, type); } +static void npcm7xx_clk_init_clock_hierarchy(NPCM7xxCLKState *s) +{ + int i; + + s->clkref = qdev_init_clock_in(DEVICE(s), "clkref", NULL, NULL); + + /* First pass: init all converter modules */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(pll_init_info_list) != NPCM7XX_CLOCK_NR_PLLS); + QEMU_BUILD_BUG_ON(ARRAY_SIZE(sel_init_info_list) != NPCM7XX_CLOCK_NR_SELS); + QEMU_BUILD_BUG_ON(ARRAY_SIZE(divider_init_info_list) + != NPCM7XX_CLOCK_NR_DIVIDERS); + for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { + object_initialize_child(OBJECT(s), pll_init_info_list[i].name, + &s->plls[i], TYPE_NPCM7XX_CLOCK_PLL); + npcm7xx_init_clock_pll(&s->plls[i], s, + &pll_init_info_list[i]); + } + for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { + object_initialize_child(OBJECT(s), sel_init_info_list[i].name, + &s->sels[i], TYPE_NPCM7XX_CLOCK_SEL); + npcm7xx_init_clock_sel(&s->sels[i], s, + &sel_init_info_list[i]); + } + for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { + object_initialize_child(OBJECT(s), divider_init_info_list[i].name, + &s->dividers[i], TYPE_NPCM7XX_CLOCK_DIVIDER); + npcm7xx_init_clock_divider(&s->dividers[i], s, + ÷r_init_info_list[i]); + } + + /* Second pass: connect converter modules */ + npcm7xx_connect_clocks(s); + + clock_update_hz(s->clkref, NPCM7XX_CLOCK_REF_HZ); +} + static void npcm7xx_clk_init(Object *obj) { NPCM7xxCLKState *s = NPCM7XX_CLK(obj); @@ -252,21 +914,114 @@ static void npcm7xx_clk_init(Object *obj) memory_region_init_io(&s->iomem, obj, &npcm7xx_clk_ops, s, TYPE_NPCM7XX_CLK, 4 * KiB); sysbus_init_mmio(&s->parent, &s->iomem); +} + +static int npcm7xx_clk_post_load(void *opaque, int version_id) +{ + if (version_id >= 1) { + NPCM7xxCLKState *clk = opaque; + + npcm7xx_clk_update_all_clocks(clk); + } + + return 0; +} + +static void npcm7xx_clk_realize(DeviceState *dev, Error **errp) +{ + int i; + NPCM7xxCLKState *s = NPCM7XX_CLK(dev); + qdev_init_gpio_in_named(DEVICE(s), npcm7xx_clk_perform_watchdog_reset, NPCM7XX_WATCHDOG_RESET_GPIO_IN, NPCM7XX_NR_WATCHDOGS); + npcm7xx_clk_init_clock_hierarchy(s); + + /* Realize child devices */ + for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { + if (!qdev_realize(DEVICE(&s->plls[i]), NULL, errp)) { + return; + } + } + for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { + if (!qdev_realize(DEVICE(&s->sels[i]), NULL, errp)) { + return; + } + } + for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { + if (!qdev_realize(DEVICE(&s->dividers[i]), NULL, errp)) { + return; + } + } } -static const VMStateDescription vmstate_npcm7xx_clk = { - .name = "npcm7xx-clk", +static const VMStateDescription vmstate_npcm7xx_clk_pll = { + .name = "npcm7xx-clock-pll", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_CLOCK(clock_in, NPCM7xxClockPLLState), + VMSTATE_END_OF_LIST(), + }, +}; + +static const VMStateDescription vmstate_npcm7xx_clk_sel = { + .name = "npcm7xx-clock-sel", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_ARRAY_OF_POINTER_TO_STRUCT(clock_in, NPCM7xxClockSELState, + NPCM7XX_CLK_SEL_MAX_INPUT, 0, vmstate_clock, Clock), + VMSTATE_END_OF_LIST(), + }, +}; + +static const VMStateDescription vmstate_npcm7xx_clk_divider = { + .name = "npcm7xx-clock-divider", .version_id = 0, .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_CLOCK(clock_in, NPCM7xxClockDividerState), + VMSTATE_END_OF_LIST(), + }, +}; + +static const VMStateDescription vmstate_npcm7xx_clk = { + .name = "npcm7xx-clk", + .version_id = 1, + .minimum_version_id = 1, + .post_load = npcm7xx_clk_post_load, .fields = (VMStateField[]) { VMSTATE_UINT32_ARRAY(regs, NPCM7xxCLKState, NPCM7XX_CLK_NR_REGS), VMSTATE_INT64(ref_ns, NPCM7xxCLKState), + VMSTATE_CLOCK(clkref, NPCM7xxCLKState), VMSTATE_END_OF_LIST(), }, }; +static void npcm7xx_clk_pll_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM7xx Clock PLL Module"; + dc->vmsd = &vmstate_npcm7xx_clk_pll; +} + +static void npcm7xx_clk_sel_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM7xx Clock SEL Module"; + dc->vmsd = &vmstate_npcm7xx_clk_sel; +} + +static void npcm7xx_clk_divider_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM7xx Clock Divider Module"; + dc->vmsd = &vmstate_npcm7xx_clk_divider; +} + static void npcm7xx_clk_class_init(ObjectClass *klass, void *data) { ResettableClass *rc = RESETTABLE_CLASS(klass); @@ -276,9 +1031,34 @@ static void npcm7xx_clk_class_init(ObjectClass *klass, void *data) dc->desc = "NPCM7xx Clock Control Registers"; dc->vmsd = &vmstate_npcm7xx_clk; + dc->realize = npcm7xx_clk_realize; rc->phases.enter = npcm7xx_clk_enter_reset; } +static const TypeInfo npcm7xx_clk_pll_info = { + .name = TYPE_NPCM7XX_CLOCK_PLL, + .parent = TYPE_DEVICE, + .instance_size = sizeof(NPCM7xxClockPLLState), + .instance_init = npcm7xx_clk_pll_init, + .class_init = npcm7xx_clk_pll_class_init, +}; + +static const TypeInfo npcm7xx_clk_sel_info = { + .name = TYPE_NPCM7XX_CLOCK_SEL, + .parent = TYPE_DEVICE, + .instance_size = sizeof(NPCM7xxClockSELState), + .instance_init = npcm7xx_clk_sel_init, + .class_init = npcm7xx_clk_sel_class_init, +}; + +static const TypeInfo npcm7xx_clk_divider_info = { + .name = TYPE_NPCM7XX_CLOCK_DIVIDER, + .parent = TYPE_DEVICE, + .instance_size = sizeof(NPCM7xxClockDividerState), + .instance_init = npcm7xx_clk_divider_init, + .class_init = npcm7xx_clk_divider_class_init, +}; + static const TypeInfo npcm7xx_clk_info = { .name = TYPE_NPCM7XX_CLK, .parent = TYPE_SYS_BUS_DEVICE, @@ -289,6 +1069,9 @@ static const TypeInfo npcm7xx_clk_info = { static void npcm7xx_clk_register_type(void) { + type_register_static(&npcm7xx_clk_pll_info); + type_register_static(&npcm7xx_clk_sel_info); + type_register_static(&npcm7xx_clk_divider_info); type_register_static(&npcm7xx_clk_info); } type_init(npcm7xx_clk_register_type); diff --git a/include/hw/misc/npcm7xx_clk.h b/include/hw/misc/npcm7xx_clk.h index 2338fbbdb5..f641f95f3e 100644 --- a/include/hw/misc/npcm7xx_clk.h +++ b/include/hw/misc/npcm7xx_clk.h @@ -17,6 +17,7 @@ #define NPCM7XX_CLK_H #include "exec/memory.h" +#include "hw/clock.h" #include "hw/sysbus.h" /* @@ -33,16 +34,151 @@ #define NPCM7XX_WATCHDOG_RESET_GPIO_IN "npcm7xx-clk-watchdog-reset-gpio-in" -typedef struct NPCM7xxCLKState { +/* Maximum amount of clock inputs in a SEL module. */ +#define NPCM7XX_CLK_SEL_MAX_INPUT 5 + +/* PLLs in CLK module. */ +typedef enum NPCM7xxClockPLL { + NPCM7XX_CLOCK_PLL0, + NPCM7XX_CLOCK_PLL1, + NPCM7XX_CLOCK_PLL2, + NPCM7XX_CLOCK_PLLG, + NPCM7XX_CLOCK_NR_PLLS, +} NPCM7xxClockPLL; + +/* SEL/MUX in CLK module. */ +typedef enum NPCM7xxClockSEL { + NPCM7XX_CLOCK_PIXCKSEL, + NPCM7XX_CLOCK_MCCKSEL, + NPCM7XX_CLOCK_CPUCKSEL, + NPCM7XX_CLOCK_CLKOUTSEL, + NPCM7XX_CLOCK_UARTCKSEL, + NPCM7XX_CLOCK_TIMCKSEL, + NPCM7XX_CLOCK_SDCKSEL, + NPCM7XX_CLOCK_GFXMSEL, + NPCM7XX_CLOCK_SUCKSEL, + NPCM7XX_CLOCK_NR_SELS, +} NPCM7xxClockSEL; + +/* Dividers in CLK module. */ +typedef enum NPCM7xxClockDivider { + NPCM7XX_CLOCK_PLL1D2, /* PLL1/2 */ + NPCM7XX_CLOCK_PLL2D2, /* PLL2/2 */ + NPCM7XX_CLOCK_MC_DIVIDER, + NPCM7XX_CLOCK_AXI_DIVIDER, + NPCM7XX_CLOCK_AHB_DIVIDER, + NPCM7XX_CLOCK_AHB3_DIVIDER, + NPCM7XX_CLOCK_SPI0_DIVIDER, + NPCM7XX_CLOCK_SPIX_DIVIDER, + NPCM7XX_CLOCK_APB1_DIVIDER, + NPCM7XX_CLOCK_APB2_DIVIDER, + NPCM7XX_CLOCK_APB3_DIVIDER, + NPCM7XX_CLOCK_APB4_DIVIDER, + NPCM7XX_CLOCK_APB5_DIVIDER, + NPCM7XX_CLOCK_CLKOUT_DIVIDER, + NPCM7XX_CLOCK_UART_DIVIDER, + NPCM7XX_CLOCK_TIMER_DIVIDER, + NPCM7XX_CLOCK_ADC_DIVIDER, + NPCM7XX_CLOCK_MMC_DIVIDER, + NPCM7XX_CLOCK_SDHC_DIVIDER, + NPCM7XX_CLOCK_GFXM_DIVIDER, /* divide by 3 */ + NPCM7XX_CLOCK_UTMI_DIVIDER, + NPCM7XX_CLOCK_NR_DIVIDERS, +} NPCM7xxClockConverter; + +typedef struct NPCM7xxCLKState NPCM7xxCLKState; + +/** + * struct NPCM7xxClockPLLState - A PLL module in CLK module. + * @name: The name of the module. + * @clk: The CLK module that owns this module. + * @clock_in: The input clock of this module. + * @clock_out: The output clock of this module. + * @reg: The control registers for this PLL module. + */ +typedef struct NPCM7xxClockPLLState { + DeviceState parent; + + const char *name; + NPCM7xxCLKState *clk; + Clock *clock_in; + Clock *clock_out; + + int reg; +} NPCM7xxClockPLLState; + +/** + * struct NPCM7xxClockSELState - A SEL module in CLK module. + * @name: The name of the module. + * @clk: The CLK module that owns this module. + * @input_size: The size of inputs of this module. + * @clock_in: The input clocks of this module. + * @clock_out: The output clocks of this module. + * @offset: The offset of this module in the control register. + * @len: The length of this module in the control register. + */ +typedef struct NPCM7xxClockSELState { + DeviceState parent; + + const char *name; + NPCM7xxCLKState *clk; + uint8_t input_size; + Clock *clock_in[NPCM7XX_CLK_SEL_MAX_INPUT]; + Clock *clock_out; + + int offset; + int len; +} NPCM7xxClockSELState; + +/** + * struct NPCM7xxClockDividerState - A Divider module in CLK module. + * @name: The name of the module. + * @clk: The CLK module that owns this module. + * @clock_in: The input clock of this module. + * @clock_out: The output clock of this module. + * @divide: The function the divider uses to divide the input. + * @reg: The index of the control register that contains the divisor. + * @offset: The offset of the divisor in the control register. + * @len: The length of the divisor in the control register. + * @divisor: The divisor for a constant divisor + */ +typedef struct NPCM7xxClockDividerState { + DeviceState parent; + + const char *name; + NPCM7xxCLKState *clk; + Clock *clock_in; + Clock *clock_out; + + uint32_t (*divide)(struct NPCM7xxClockDividerState *s); + union { + struct { + int reg; + int offset; + int len; + }; + int divisor; + }; +} NPCM7xxClockDividerState; + +struct NPCM7xxCLKState { SysBusDevice parent; MemoryRegion iomem; + /* Clock converters */ + NPCM7xxClockPLLState plls[NPCM7XX_CLOCK_NR_PLLS]; + NPCM7xxClockSELState sels[NPCM7XX_CLOCK_NR_SELS]; + NPCM7xxClockDividerState dividers[NPCM7XX_CLOCK_NR_DIVIDERS]; + uint32_t regs[NPCM7XX_CLK_NR_REGS]; /* Time reference for SECCNT and CNTR25M, initialized by power on reset */ int64_t ref_ns; -} NPCM7xxCLKState; + + /* The incoming reference clock. */ + Clock *clkref; +}; #define TYPE_NPCM7XX_CLK "npcm7xx-clk" #define NPCM7XX_CLK(obj) OBJECT_CHECK(NPCM7xxCLKState, (obj), TYPE_NPCM7XX_CLK)