/* * Nuvoton NPCM7xx Clock Control Registers. * * Copyright 2020 Google LLC * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "qemu/osdep.h" #include "hw/misc/npcm7xx_clk.h" #include "migration/vmstate.h" #include "qemu/error-report.h" #include "qemu/log.h" #include "qemu/module.h" #include "qemu/timer.h" #include "qemu/units.h" #include "trace.h" #define PLLCON_LOKI BIT(31) #define PLLCON_LOKS BIT(30) #define PLLCON_PWDEN BIT(12) enum NPCM7xxCLKRegisters { NPCM7XX_CLK_CLKEN1, NPCM7XX_CLK_CLKSEL, NPCM7XX_CLK_CLKDIV1, NPCM7XX_CLK_PLLCON0, NPCM7XX_CLK_PLLCON1, NPCM7XX_CLK_SWRSTR, NPCM7XX_CLK_IPSRST1 = 0x20 / sizeof(uint32_t), NPCM7XX_CLK_IPSRST2, NPCM7XX_CLK_CLKEN2, NPCM7XX_CLK_CLKDIV2, NPCM7XX_CLK_CLKEN3, NPCM7XX_CLK_IPSRST3, NPCM7XX_CLK_WD0RCR, NPCM7XX_CLK_WD1RCR, NPCM7XX_CLK_WD2RCR, NPCM7XX_CLK_SWRSTC1, NPCM7XX_CLK_SWRSTC2, NPCM7XX_CLK_SWRSTC3, NPCM7XX_CLK_SWRSTC4, NPCM7XX_CLK_PLLCON2, NPCM7XX_CLK_CLKDIV3, NPCM7XX_CLK_CORSTC, NPCM7XX_CLK_PLLCONG, NPCM7XX_CLK_AHBCKFI, NPCM7XX_CLK_SECCNT, NPCM7XX_CLK_CNTR25M, NPCM7XX_CLK_REGS_END, }; /* * These reset values were taken from version 0.91 of the NPCM750R data sheet. * * All are loaded on power-up reset. CLKENx and SWRSTR should also be loaded on * core domain reset, but this reset type is not yet supported by QEMU. */ static const uint32_t cold_reset_values[NPCM7XX_CLK_NR_REGS] = { [NPCM7XX_CLK_CLKEN1] = 0xffffffff, [NPCM7XX_CLK_CLKSEL] = 0x004aaaaa, [NPCM7XX_CLK_CLKDIV1] = 0x5413f855, [NPCM7XX_CLK_PLLCON0] = 0x00222101 | PLLCON_LOKI, [NPCM7XX_CLK_PLLCON1] = 0x00202101 | PLLCON_LOKI, [NPCM7XX_CLK_IPSRST1] = 0x00001000, [NPCM7XX_CLK_IPSRST2] = 0x80000000, [NPCM7XX_CLK_CLKEN2] = 0xffffffff, [NPCM7XX_CLK_CLKDIV2] = 0xaa4f8f9f, [NPCM7XX_CLK_CLKEN3] = 0xffffffff, [NPCM7XX_CLK_IPSRST3] = 0x03000000, [NPCM7XX_CLK_WD0RCR] = 0xffffffff, [NPCM7XX_CLK_WD1RCR] = 0xffffffff, [NPCM7XX_CLK_WD2RCR] = 0xffffffff, [NPCM7XX_CLK_SWRSTC1] = 0x00000003, [NPCM7XX_CLK_PLLCON2] = 0x00c02105 | PLLCON_LOKI, [NPCM7XX_CLK_CORSTC] = 0x04000003, [NPCM7XX_CLK_PLLCONG] = 0x01228606 | PLLCON_LOKI, [NPCM7XX_CLK_AHBCKFI] = 0x000000c8, }; static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size) { uint32_t reg = offset / sizeof(uint32_t); NPCM7xxCLKState *s = opaque; int64_t now_ns; uint32_t value = 0; if (reg >= NPCM7XX_CLK_NR_REGS) { qemu_log_mask(LOG_GUEST_ERROR, "%s: offset 0x%04" HWADDR_PRIx " out of range\n", __func__, offset); return 0; } switch (reg) { case NPCM7XX_CLK_SWRSTR: qemu_log_mask(LOG_GUEST_ERROR, "%s: register @ 0x%04" HWADDR_PRIx " is write-only\n", __func__, offset); break; case NPCM7XX_CLK_SECCNT: now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); value = (now_ns - s->ref_ns) / NANOSECONDS_PER_SECOND; break; case NPCM7XX_CLK_CNTR25M: now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); /* * This register counts 25 MHz cycles, updating every 640 ns. It rolls * over to zero every second. * * The 4 LSBs are always zero: (1e9 / 640) << 4 = 25000000. */ value = (((now_ns - s->ref_ns) / 640) << 4) % NPCM7XX_TIMER_REF_HZ; break; default: value = s->regs[reg]; break; }; trace_npcm7xx_clk_read(offset, value); return value; } static void npcm7xx_clk_write(void *opaque, hwaddr offset, uint64_t v, unsigned size) { uint32_t reg = offset / sizeof(uint32_t); NPCM7xxCLKState *s = opaque; uint32_t value = v; trace_npcm7xx_clk_write(offset, value); if (reg >= NPCM7XX_CLK_NR_REGS) { qemu_log_mask(LOG_GUEST_ERROR, "%s: offset 0x%04" HWADDR_PRIx " out of range\n", __func__, offset); return; } switch (reg) { case NPCM7XX_CLK_SWRSTR: qemu_log_mask(LOG_UNIMP, "%s: SW reset not implemented: 0x%02x\n", __func__, value); value = 0; break; case NPCM7XX_CLK_PLLCON0: case NPCM7XX_CLK_PLLCON1: case NPCM7XX_CLK_PLLCON2: case NPCM7XX_CLK_PLLCONG: if (value & PLLCON_PWDEN) { /* Power down -- clear lock and indicate loss of lock */ value &= ~PLLCON_LOKI; value |= PLLCON_LOKS; } else { /* Normal mode -- assume always locked */ value |= PLLCON_LOKI; /* Keep LOKS unchanged unless cleared by writing 1 */ if (value & PLLCON_LOKS) { value &= ~PLLCON_LOKS; } else { value |= (value & PLLCON_LOKS); } } break; case NPCM7XX_CLK_CNTR25M: qemu_log_mask(LOG_GUEST_ERROR, "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n", __func__, offset); return; } s->regs[reg] = value; } static const struct MemoryRegionOps npcm7xx_clk_ops = { .read = npcm7xx_clk_read, .write = npcm7xx_clk_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, .unaligned = false, }, }; static void npcm7xx_clk_enter_reset(Object *obj, ResetType type) { NPCM7xxCLKState *s = NPCM7XX_CLK(obj); QEMU_BUILD_BUG_ON(sizeof(s->regs) != sizeof(cold_reset_values)); switch (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); return; } /* * A small number of registers need to be reset on a core domain reset, * but no such reset type exists yet. */ qemu_log_mask(LOG_UNIMP, "%s: reset type %d not implemented.", __func__, type); } static void npcm7xx_clk_init(Object *obj) { NPCM7xxCLKState *s = NPCM7XX_CLK(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 const VMStateDescription vmstate_npcm7xx_clk = { .name = "npcm7xx-clk", .version_id = 0, .minimum_version_id = 0, .fields = (VMStateField[]) { VMSTATE_UINT32_ARRAY(regs, NPCM7xxCLKState, NPCM7XX_CLK_NR_REGS), VMSTATE_INT64(ref_ns, NPCM7xxCLKState), VMSTATE_END_OF_LIST(), }, }; static void npcm7xx_clk_class_init(ObjectClass *klass, void *data) { ResettableClass *rc = RESETTABLE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); QEMU_BUILD_BUG_ON(NPCM7XX_CLK_REGS_END > NPCM7XX_CLK_NR_REGS); dc->desc = "NPCM7xx Clock Control Registers"; dc->vmsd = &vmstate_npcm7xx_clk; rc->phases.enter = npcm7xx_clk_enter_reset; } static const TypeInfo npcm7xx_clk_info = { .name = TYPE_NPCM7XX_CLK, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(NPCM7xxCLKState), .instance_init = npcm7xx_clk_init, .class_init = npcm7xx_clk_class_init, }; static void npcm7xx_clk_register_type(void) { type_register_static(&npcm7xx_clk_info); } type_init(npcm7xx_clk_register_type);