1dc2a070dSqianfan Zhao /*
2dc2a070dSqianfan Zhao * Allwinner R40 Clock Control Unit emulation
3dc2a070dSqianfan Zhao *
4dc2a070dSqianfan Zhao * Copyright (C) 2023 qianfan Zhao <qianfanguijin@163.com>
5dc2a070dSqianfan Zhao *
6dc2a070dSqianfan Zhao * This program is free software: you can redistribute it and/or modify
7dc2a070dSqianfan Zhao * it under the terms of the GNU General Public License as published by
8dc2a070dSqianfan Zhao * the Free Software Foundation, either version 2 of the License, or
9dc2a070dSqianfan Zhao * (at your option) any later version.
10dc2a070dSqianfan Zhao *
11dc2a070dSqianfan Zhao * This program is distributed in the hope that it will be useful,
12dc2a070dSqianfan Zhao * but WITHOUT ANY WARRANTY; without even the implied warranty of
13dc2a070dSqianfan Zhao * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14dc2a070dSqianfan Zhao * GNU General Public License for more details.
15dc2a070dSqianfan Zhao *
16dc2a070dSqianfan Zhao * You should have received a copy of the GNU General Public License
17dc2a070dSqianfan Zhao * along with this program. If not, see <http://www.gnu.org/licenses/>.
18dc2a070dSqianfan Zhao */
19dc2a070dSqianfan Zhao
20dc2a070dSqianfan Zhao #include "qemu/osdep.h"
21dc2a070dSqianfan Zhao #include "qemu/units.h"
22dc2a070dSqianfan Zhao #include "hw/sysbus.h"
23dc2a070dSqianfan Zhao #include "migration/vmstate.h"
24dc2a070dSqianfan Zhao #include "qemu/log.h"
25dc2a070dSqianfan Zhao #include "qemu/module.h"
26dc2a070dSqianfan Zhao #include "hw/misc/allwinner-r40-ccu.h"
27dc2a070dSqianfan Zhao
28dc2a070dSqianfan Zhao /* CCU register offsets */
29dc2a070dSqianfan Zhao enum {
30dc2a070dSqianfan Zhao REG_PLL_CPUX_CTRL = 0x0000,
31dc2a070dSqianfan Zhao REG_PLL_AUDIO_CTRL = 0x0008,
32dc2a070dSqianfan Zhao REG_PLL_VIDEO0_CTRL = 0x0010,
33dc2a070dSqianfan Zhao REG_PLL_VE_CTRL = 0x0018,
34dc2a070dSqianfan Zhao REG_PLL_DDR0_CTRL = 0x0020,
35dc2a070dSqianfan Zhao REG_PLL_PERIPH0_CTRL = 0x0028,
36dc2a070dSqianfan Zhao REG_PLL_PERIPH1_CTRL = 0x002c,
37dc2a070dSqianfan Zhao REG_PLL_VIDEO1_CTRL = 0x0030,
38dc2a070dSqianfan Zhao REG_PLL_SATA_CTRL = 0x0034,
39dc2a070dSqianfan Zhao REG_PLL_GPU_CTRL = 0x0038,
40dc2a070dSqianfan Zhao REG_PLL_MIPI_CTRL = 0x0040,
41dc2a070dSqianfan Zhao REG_PLL_DE_CTRL = 0x0048,
42dc2a070dSqianfan Zhao REG_PLL_DDR1_CTRL = 0x004c,
43dc2a070dSqianfan Zhao REG_AHB1_APB1_CFG = 0x0054,
44dc2a070dSqianfan Zhao REG_APB2_CFG = 0x0058,
45dc2a070dSqianfan Zhao REG_MMC0_CLK = 0x0088,
46dc2a070dSqianfan Zhao REG_MMC1_CLK = 0x008c,
47dc2a070dSqianfan Zhao REG_MMC2_CLK = 0x0090,
48dc2a070dSqianfan Zhao REG_MMC3_CLK = 0x0094,
49dc2a070dSqianfan Zhao REG_USBPHY_CFG = 0x00cc,
50dc2a070dSqianfan Zhao REG_PLL_DDR_AUX = 0x00f0,
51dc2a070dSqianfan Zhao REG_DRAM_CFG = 0x00f4,
52dc2a070dSqianfan Zhao REG_PLL_DDR1_CFG = 0x00f8,
53dc2a070dSqianfan Zhao REG_DRAM_CLK_GATING = 0x0100,
54dc2a070dSqianfan Zhao REG_GMAC_CLK = 0x0164,
55dc2a070dSqianfan Zhao REG_SYS_32K_CLK = 0x0310,
56dc2a070dSqianfan Zhao REG_PLL_LOCK_CTRL = 0x0320,
57dc2a070dSqianfan Zhao };
58dc2a070dSqianfan Zhao
59dc2a070dSqianfan Zhao #define REG_INDEX(offset) (offset / sizeof(uint32_t))
60dc2a070dSqianfan Zhao
61dc2a070dSqianfan Zhao /* CCU register flags */
62dc2a070dSqianfan Zhao enum {
63dc2a070dSqianfan Zhao REG_PLL_ENABLE = (1 << 31),
64dc2a070dSqianfan Zhao REG_PLL_LOCK = (1 << 28),
65dc2a070dSqianfan Zhao };
66dc2a070dSqianfan Zhao
allwinner_r40_ccu_read(void * opaque,hwaddr offset,unsigned size)67dc2a070dSqianfan Zhao static uint64_t allwinner_r40_ccu_read(void *opaque, hwaddr offset,
68dc2a070dSqianfan Zhao unsigned size)
69dc2a070dSqianfan Zhao {
70dc2a070dSqianfan Zhao const AwR40ClockCtlState *s = AW_R40_CCU(opaque);
71dc2a070dSqianfan Zhao const uint32_t idx = REG_INDEX(offset);
72dc2a070dSqianfan Zhao
73dc2a070dSqianfan Zhao switch (offset) {
74dc2a070dSqianfan Zhao case 0x324 ... AW_R40_CCU_IOSIZE:
75dc2a070dSqianfan Zhao qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
76dc2a070dSqianfan Zhao __func__, (uint32_t)offset);
77dc2a070dSqianfan Zhao return 0;
78dc2a070dSqianfan Zhao }
79dc2a070dSqianfan Zhao
80dc2a070dSqianfan Zhao return s->regs[idx];
81dc2a070dSqianfan Zhao }
82dc2a070dSqianfan Zhao
allwinner_r40_ccu_write(void * opaque,hwaddr offset,uint64_t val,unsigned size)83dc2a070dSqianfan Zhao static void allwinner_r40_ccu_write(void *opaque, hwaddr offset,
84dc2a070dSqianfan Zhao uint64_t val, unsigned size)
85dc2a070dSqianfan Zhao {
86dc2a070dSqianfan Zhao AwR40ClockCtlState *s = AW_R40_CCU(opaque);
87dc2a070dSqianfan Zhao
88dc2a070dSqianfan Zhao switch (offset) {
89dc2a070dSqianfan Zhao case REG_DRAM_CFG: /* DRAM Configuration(for DDR0) */
90dc2a070dSqianfan Zhao /* bit16: SDRCLK_UPD (SDRCLK configuration 0 update) */
91dc2a070dSqianfan Zhao val &= ~(1 << 16);
92dc2a070dSqianfan Zhao break;
93dc2a070dSqianfan Zhao case REG_PLL_DDR1_CTRL: /* DDR1 Control register */
94dc2a070dSqianfan Zhao /* bit30: SDRPLL_UPD */
95dc2a070dSqianfan Zhao val &= ~(1 << 30);
96dc2a070dSqianfan Zhao if (val & REG_PLL_ENABLE) {
97dc2a070dSqianfan Zhao val |= REG_PLL_LOCK;
98dc2a070dSqianfan Zhao }
99dc2a070dSqianfan Zhao break;
100dc2a070dSqianfan Zhao case REG_PLL_CPUX_CTRL:
101dc2a070dSqianfan Zhao case REG_PLL_AUDIO_CTRL:
102dc2a070dSqianfan Zhao case REG_PLL_VE_CTRL:
103dc2a070dSqianfan Zhao case REG_PLL_VIDEO0_CTRL:
104dc2a070dSqianfan Zhao case REG_PLL_DDR0_CTRL:
105dc2a070dSqianfan Zhao case REG_PLL_PERIPH0_CTRL:
106dc2a070dSqianfan Zhao case REG_PLL_PERIPH1_CTRL:
107dc2a070dSqianfan Zhao case REG_PLL_VIDEO1_CTRL:
108dc2a070dSqianfan Zhao case REG_PLL_SATA_CTRL:
109dc2a070dSqianfan Zhao case REG_PLL_GPU_CTRL:
110dc2a070dSqianfan Zhao case REG_PLL_MIPI_CTRL:
111dc2a070dSqianfan Zhao case REG_PLL_DE_CTRL:
112dc2a070dSqianfan Zhao if (val & REG_PLL_ENABLE) {
113dc2a070dSqianfan Zhao val |= REG_PLL_LOCK;
114dc2a070dSqianfan Zhao }
115dc2a070dSqianfan Zhao break;
116dc2a070dSqianfan Zhao case 0x324 ... AW_R40_CCU_IOSIZE:
117dc2a070dSqianfan Zhao qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
118dc2a070dSqianfan Zhao __func__, (uint32_t)offset);
119dc2a070dSqianfan Zhao break;
120dc2a070dSqianfan Zhao default:
121dc2a070dSqianfan Zhao qemu_log_mask(LOG_UNIMP, "%s: unimplemented write offset 0x%04x\n",
122dc2a070dSqianfan Zhao __func__, (uint32_t)offset);
123dc2a070dSqianfan Zhao break;
124dc2a070dSqianfan Zhao }
125dc2a070dSqianfan Zhao
126dc2a070dSqianfan Zhao s->regs[REG_INDEX(offset)] = (uint32_t) val;
127dc2a070dSqianfan Zhao }
128dc2a070dSqianfan Zhao
129dc2a070dSqianfan Zhao static const MemoryRegionOps allwinner_r40_ccu_ops = {
130dc2a070dSqianfan Zhao .read = allwinner_r40_ccu_read,
131dc2a070dSqianfan Zhao .write = allwinner_r40_ccu_write,
132dc2a070dSqianfan Zhao .endianness = DEVICE_NATIVE_ENDIAN,
133dc2a070dSqianfan Zhao .valid = {
134dc2a070dSqianfan Zhao .min_access_size = 4,
135dc2a070dSqianfan Zhao .max_access_size = 4,
136dc2a070dSqianfan Zhao },
137dc2a070dSqianfan Zhao .impl.min_access_size = 4,
138dc2a070dSqianfan Zhao };
139dc2a070dSqianfan Zhao
allwinner_r40_ccu_reset(DeviceState * dev)140dc2a070dSqianfan Zhao static void allwinner_r40_ccu_reset(DeviceState *dev)
141dc2a070dSqianfan Zhao {
142dc2a070dSqianfan Zhao AwR40ClockCtlState *s = AW_R40_CCU(dev);
143dc2a070dSqianfan Zhao
144dc2a070dSqianfan Zhao memset(s->regs, 0, sizeof(s->regs));
145dc2a070dSqianfan Zhao
146dc2a070dSqianfan Zhao /* Set default values for registers */
147dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_CPUX_CTRL)] = 0x00001000;
148dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_AUDIO_CTRL)] = 0x00035514;
149dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_VIDEO0_CTRL)] = 0x03006207;
150dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_VE_CTRL)] = 0x03006207;
151dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_DDR0_CTRL)] = 0x00001000,
152dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_PERIPH0_CTRL)] = 0x00041811;
153dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_PERIPH1_CTRL)] = 0x00041811;
154dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_VIDEO1_CTRL)] = 0x03006207;
155dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_SATA_CTRL)] = 0x00001811;
156dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_GPU_CTRL)] = 0x03006207;
157dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_MIPI_CTRL)] = 0x00000515;
158dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_DE_CTRL)] = 0x03006207;
159dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_DDR1_CTRL)] = 0x00001800;
160dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_AHB1_APB1_CFG)] = 0x00001010;
161dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_APB2_CFG)] = 0x01000000;
162dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_DDR_AUX)] = 0x00000001;
163dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_PLL_DDR1_CFG)] = 0x0ccca000;
164dc2a070dSqianfan Zhao s->regs[REG_INDEX(REG_SYS_32K_CLK)] = 0x0000000f;
165dc2a070dSqianfan Zhao }
166dc2a070dSqianfan Zhao
allwinner_r40_ccu_init(Object * obj)167dc2a070dSqianfan Zhao static void allwinner_r40_ccu_init(Object *obj)
168dc2a070dSqianfan Zhao {
169dc2a070dSqianfan Zhao SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
170dc2a070dSqianfan Zhao AwR40ClockCtlState *s = AW_R40_CCU(obj);
171dc2a070dSqianfan Zhao
172dc2a070dSqianfan Zhao /* Memory mapping */
173dc2a070dSqianfan Zhao memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_r40_ccu_ops, s,
174dc2a070dSqianfan Zhao TYPE_AW_R40_CCU, AW_R40_CCU_IOSIZE);
175dc2a070dSqianfan Zhao sysbus_init_mmio(sbd, &s->iomem);
176dc2a070dSqianfan Zhao }
177dc2a070dSqianfan Zhao
178dc2a070dSqianfan Zhao static const VMStateDescription allwinner_r40_ccu_vmstate = {
179dc2a070dSqianfan Zhao .name = "allwinner-r40-ccu",
180dc2a070dSqianfan Zhao .version_id = 1,
181dc2a070dSqianfan Zhao .minimum_version_id = 1,
182e4ea952fSRichard Henderson .fields = (const VMStateField[]) {
183dc2a070dSqianfan Zhao VMSTATE_UINT32_ARRAY(regs, AwR40ClockCtlState, AW_R40_CCU_REGS_NUM),
184dc2a070dSqianfan Zhao VMSTATE_END_OF_LIST()
185dc2a070dSqianfan Zhao }
186dc2a070dSqianfan Zhao };
187dc2a070dSqianfan Zhao
allwinner_r40_ccu_class_init(ObjectClass * klass,void * data)188dc2a070dSqianfan Zhao static void allwinner_r40_ccu_class_init(ObjectClass *klass, void *data)
189dc2a070dSqianfan Zhao {
190dc2a070dSqianfan Zhao DeviceClass *dc = DEVICE_CLASS(klass);
191dc2a070dSqianfan Zhao
192*e3d08143SPeter Maydell device_class_set_legacy_reset(dc, allwinner_r40_ccu_reset);
193dc2a070dSqianfan Zhao dc->vmsd = &allwinner_r40_ccu_vmstate;
194dc2a070dSqianfan Zhao }
195dc2a070dSqianfan Zhao
196dc2a070dSqianfan Zhao static const TypeInfo allwinner_r40_ccu_info = {
197dc2a070dSqianfan Zhao .name = TYPE_AW_R40_CCU,
198dc2a070dSqianfan Zhao .parent = TYPE_SYS_BUS_DEVICE,
199dc2a070dSqianfan Zhao .instance_init = allwinner_r40_ccu_init,
200dc2a070dSqianfan Zhao .instance_size = sizeof(AwR40ClockCtlState),
201dc2a070dSqianfan Zhao .class_init = allwinner_r40_ccu_class_init,
202dc2a070dSqianfan Zhao };
203dc2a070dSqianfan Zhao
allwinner_r40_ccu_register(void)204dc2a070dSqianfan Zhao static void allwinner_r40_ccu_register(void)
205dc2a070dSqianfan Zhao {
206dc2a070dSqianfan Zhao type_register_static(&allwinner_r40_ccu_info);
207dc2a070dSqianfan Zhao }
208dc2a070dSqianfan Zhao
209dc2a070dSqianfan Zhao type_init(allwinner_r40_ccu_register)
210