]>
Commit | Line | Data |
---|---|---|
e5a7ba87 HS |
1 | /* |
2 | * Nuvoton NPCM7xx System Global 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/npcm7xx_gcr.h" | |
20 | #include "hw/qdev-properties.h" | |
21 | #include "migration/vmstate.h" | |
22 | #include "qapi/error.h" | |
23 | #include "qemu/cutils.h" | |
24 | #include "qemu/log.h" | |
25 | #include "qemu/module.h" | |
26 | #include "qemu/units.h" | |
27 | ||
28 | #include "trace.h" | |
29 | ||
30 | #define NPCM7XX_GCR_MIN_DRAM_SIZE (128 * MiB) | |
31 | #define NPCM7XX_GCR_MAX_DRAM_SIZE (2 * GiB) | |
32 | ||
33 | enum NPCM7xxGCRRegisters { | |
34 | NPCM7XX_GCR_PDID, | |
35 | NPCM7XX_GCR_PWRON, | |
36 | NPCM7XX_GCR_MFSEL1 = 0x0c / sizeof(uint32_t), | |
37 | NPCM7XX_GCR_MFSEL2, | |
38 | NPCM7XX_GCR_MISCPE, | |
39 | NPCM7XX_GCR_SPSWC = 0x038 / sizeof(uint32_t), | |
40 | NPCM7XX_GCR_INTCR, | |
41 | NPCM7XX_GCR_INTSR, | |
42 | NPCM7XX_GCR_HIFCR = 0x050 / sizeof(uint32_t), | |
43 | NPCM7XX_GCR_INTCR2 = 0x060 / sizeof(uint32_t), | |
44 | NPCM7XX_GCR_MFSEL3, | |
45 | NPCM7XX_GCR_SRCNT, | |
46 | NPCM7XX_GCR_RESSR, | |
47 | NPCM7XX_GCR_RLOCKR1, | |
48 | NPCM7XX_GCR_FLOCKR1, | |
49 | NPCM7XX_GCR_DSCNT, | |
50 | NPCM7XX_GCR_MDLR, | |
51 | NPCM7XX_GCR_SCRPAD3, | |
52 | NPCM7XX_GCR_SCRPAD2, | |
53 | NPCM7XX_GCR_DAVCLVLR = 0x098 / sizeof(uint32_t), | |
54 | NPCM7XX_GCR_INTCR3, | |
55 | NPCM7XX_GCR_VSINTR = 0x0ac / sizeof(uint32_t), | |
56 | NPCM7XX_GCR_MFSEL4, | |
57 | NPCM7XX_GCR_CPBPNTR = 0x0c4 / sizeof(uint32_t), | |
58 | NPCM7XX_GCR_CPCTL = 0x0d0 / sizeof(uint32_t), | |
59 | NPCM7XX_GCR_CP2BST, | |
60 | NPCM7XX_GCR_B2CPNT, | |
61 | NPCM7XX_GCR_CPPCTL, | |
62 | NPCM7XX_GCR_I2CSEGSEL, | |
63 | NPCM7XX_GCR_I2CSEGCTL, | |
64 | NPCM7XX_GCR_VSRCR, | |
65 | NPCM7XX_GCR_MLOCKR, | |
66 | NPCM7XX_GCR_SCRPAD = 0x013c / sizeof(uint32_t), | |
67 | NPCM7XX_GCR_USB1PHYCTL, | |
68 | NPCM7XX_GCR_USB2PHYCTL, | |
69 | NPCM7XX_GCR_REGS_END, | |
70 | }; | |
71 | ||
72 | static const uint32_t cold_reset_values[NPCM7XX_GCR_NR_REGS] = { | |
73 | [NPCM7XX_GCR_PDID] = 0x04a92750, /* Poleg A1 */ | |
74 | [NPCM7XX_GCR_MISCPE] = 0x0000ffff, | |
75 | [NPCM7XX_GCR_SPSWC] = 0x00000003, | |
76 | [NPCM7XX_GCR_INTCR] = 0x0000035e, | |
77 | [NPCM7XX_GCR_HIFCR] = 0x0000004e, | |
78 | [NPCM7XX_GCR_INTCR2] = (1U << 19), /* DDR initialized */ | |
79 | [NPCM7XX_GCR_RESSR] = 0x80000000, | |
80 | [NPCM7XX_GCR_DSCNT] = 0x000000c0, | |
81 | [NPCM7XX_GCR_DAVCLVLR] = 0x5a00f3cf, | |
82 | [NPCM7XX_GCR_SCRPAD] = 0x00000008, | |
83 | [NPCM7XX_GCR_USB1PHYCTL] = 0x034730e4, | |
84 | [NPCM7XX_GCR_USB2PHYCTL] = 0x034730e4, | |
85 | }; | |
86 | ||
87 | static uint64_t npcm7xx_gcr_read(void *opaque, hwaddr offset, unsigned size) | |
88 | { | |
89 | uint32_t reg = offset / sizeof(uint32_t); | |
90 | NPCM7xxGCRState *s = opaque; | |
91 | ||
92 | if (reg >= NPCM7XX_GCR_NR_REGS) { | |
93 | qemu_log_mask(LOG_GUEST_ERROR, | |
94 | "%s: offset 0x%04" HWADDR_PRIx " out of range\n", | |
95 | __func__, offset); | |
96 | return 0; | |
97 | } | |
98 | ||
99 | trace_npcm7xx_gcr_read(offset, s->regs[reg]); | |
100 | ||
101 | return s->regs[reg]; | |
102 | } | |
103 | ||
104 | static void npcm7xx_gcr_write(void *opaque, hwaddr offset, | |
105 | uint64_t v, unsigned size) | |
106 | { | |
107 | uint32_t reg = offset / sizeof(uint32_t); | |
108 | NPCM7xxGCRState *s = opaque; | |
109 | uint32_t value = v; | |
110 | ||
111 | trace_npcm7xx_gcr_write(offset, value); | |
112 | ||
113 | if (reg >= NPCM7XX_GCR_NR_REGS) { | |
114 | qemu_log_mask(LOG_GUEST_ERROR, | |
115 | "%s: offset 0x%04" HWADDR_PRIx " out of range\n", | |
116 | __func__, offset); | |
117 | return; | |
118 | } | |
119 | ||
120 | switch (reg) { | |
121 | case NPCM7XX_GCR_PDID: | |
122 | case NPCM7XX_GCR_PWRON: | |
123 | case NPCM7XX_GCR_INTSR: | |
124 | qemu_log_mask(LOG_GUEST_ERROR, | |
125 | "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n", | |
126 | __func__, offset); | |
127 | return; | |
128 | ||
129 | case NPCM7XX_GCR_RESSR: | |
130 | case NPCM7XX_GCR_CP2BST: | |
131 | /* Write 1 to clear */ | |
132 | value = s->regs[reg] & ~value; | |
133 | break; | |
134 | ||
135 | case NPCM7XX_GCR_RLOCKR1: | |
136 | case NPCM7XX_GCR_MDLR: | |
137 | /* Write 1 to set */ | |
138 | value |= s->regs[reg]; | |
139 | break; | |
140 | }; | |
141 | ||
142 | s->regs[reg] = value; | |
143 | } | |
144 | ||
145 | static const struct MemoryRegionOps npcm7xx_gcr_ops = { | |
146 | .read = npcm7xx_gcr_read, | |
147 | .write = npcm7xx_gcr_write, | |
148 | .endianness = DEVICE_LITTLE_ENDIAN, | |
149 | .valid = { | |
150 | .min_access_size = 4, | |
151 | .max_access_size = 4, | |
152 | .unaligned = false, | |
153 | }, | |
154 | }; | |
155 | ||
156 | static void npcm7xx_gcr_enter_reset(Object *obj, ResetType type) | |
157 | { | |
158 | NPCM7xxGCRState *s = NPCM7XX_GCR(obj); | |
159 | ||
160 | QEMU_BUILD_BUG_ON(sizeof(s->regs) != sizeof(cold_reset_values)); | |
161 | ||
162 | switch (type) { | |
163 | case RESET_TYPE_COLD: | |
164 | memcpy(s->regs, cold_reset_values, sizeof(s->regs)); | |
165 | s->regs[NPCM7XX_GCR_PWRON] = s->reset_pwron; | |
166 | s->regs[NPCM7XX_GCR_MDLR] = s->reset_mdlr; | |
167 | s->regs[NPCM7XX_GCR_INTCR3] = s->reset_intcr3; | |
168 | break; | |
169 | } | |
170 | } | |
171 | ||
172 | static void npcm7xx_gcr_realize(DeviceState *dev, Error **errp) | |
173 | { | |
174 | ERRP_GUARD(); | |
175 | NPCM7xxGCRState *s = NPCM7XX_GCR(dev); | |
176 | uint64_t dram_size; | |
177 | Object *obj; | |
178 | ||
179 | obj = object_property_get_link(OBJECT(dev), "dram-mr", errp); | |
180 | if (!obj) { | |
181 | error_prepend(errp, "%s: required dram-mr link not found: ", __func__); | |
182 | return; | |
183 | } | |
184 | dram_size = memory_region_size(MEMORY_REGION(obj)); | |
185 | if (!is_power_of_2(dram_size) || | |
186 | dram_size < NPCM7XX_GCR_MIN_DRAM_SIZE || | |
187 | dram_size > NPCM7XX_GCR_MAX_DRAM_SIZE) { | |
188 | g_autofree char *sz = size_to_str(dram_size); | |
189 | g_autofree char *min_sz = size_to_str(NPCM7XX_GCR_MIN_DRAM_SIZE); | |
190 | g_autofree char *max_sz = size_to_str(NPCM7XX_GCR_MAX_DRAM_SIZE); | |
191 | error_setg(errp, "%s: unsupported DRAM size %s", __func__, sz); | |
192 | error_append_hint(errp, | |
193 | "DRAM size must be a power of two between %s and %s," | |
194 | " inclusive.\n", min_sz, max_sz); | |
195 | return; | |
196 | } | |
197 | ||
198 | /* Power-on reset value */ | |
199 | s->reset_intcr3 = 0x00001002; | |
200 | ||
201 | /* | |
202 | * The GMMAP (Graphics Memory Map) field is used by u-boot to detect the | |
203 | * DRAM size, and is normally initialized by the boot block as part of DRAM | |
204 | * training. However, since we don't have a complete emulation of the | |
205 | * memory controller and try to make it look like it has already been | |
206 | * initialized, the boot block will skip this initialization, and we need | |
207 | * to make sure this field is set correctly up front. | |
208 | * | |
209 | * WARNING: some versions of u-boot only looks at bits 8 and 9, so 2 GiB of | |
210 | * DRAM will be interpreted as 128 MiB. | |
211 | * | |
212 | * https://github.com/Nuvoton-Israel/u-boot/blob/2aef993bd2aafeb5408dbaad0f3ce099ee40c4aa/board/nuvoton/poleg/poleg.c#L244 | |
213 | */ | |
214 | s->reset_intcr3 |= ctz64(dram_size / NPCM7XX_GCR_MIN_DRAM_SIZE) << 8; | |
215 | } | |
216 | ||
217 | static void npcm7xx_gcr_init(Object *obj) | |
218 | { | |
219 | NPCM7xxGCRState *s = NPCM7XX_GCR(obj); | |
220 | ||
221 | memory_region_init_io(&s->iomem, obj, &npcm7xx_gcr_ops, s, | |
222 | TYPE_NPCM7XX_GCR, 4 * KiB); | |
828d651c | 223 | sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); |
e5a7ba87 HS |
224 | } |
225 | ||
226 | static const VMStateDescription vmstate_npcm7xx_gcr = { | |
227 | .name = "npcm7xx-gcr", | |
228 | .version_id = 0, | |
229 | .minimum_version_id = 0, | |
230 | .fields = (VMStateField[]) { | |
231 | VMSTATE_UINT32_ARRAY(regs, NPCM7xxGCRState, NPCM7XX_GCR_NR_REGS), | |
232 | VMSTATE_END_OF_LIST(), | |
233 | }, | |
234 | }; | |
235 | ||
236 | static Property npcm7xx_gcr_properties[] = { | |
237 | DEFINE_PROP_UINT32("disabled-modules", NPCM7xxGCRState, reset_mdlr, 0), | |
238 | DEFINE_PROP_UINT32("power-on-straps", NPCM7xxGCRState, reset_pwron, 0), | |
239 | DEFINE_PROP_END_OF_LIST(), | |
240 | }; | |
241 | ||
242 | static void npcm7xx_gcr_class_init(ObjectClass *klass, void *data) | |
243 | { | |
244 | ResettableClass *rc = RESETTABLE_CLASS(klass); | |
245 | DeviceClass *dc = DEVICE_CLASS(klass); | |
246 | ||
247 | QEMU_BUILD_BUG_ON(NPCM7XX_GCR_REGS_END > NPCM7XX_GCR_NR_REGS); | |
248 | ||
249 | dc->desc = "NPCM7xx System Global Control Registers"; | |
250 | dc->realize = npcm7xx_gcr_realize; | |
251 | dc->vmsd = &vmstate_npcm7xx_gcr; | |
252 | rc->phases.enter = npcm7xx_gcr_enter_reset; | |
253 | ||
254 | device_class_set_props(dc, npcm7xx_gcr_properties); | |
255 | } | |
256 | ||
257 | static const TypeInfo npcm7xx_gcr_info = { | |
258 | .name = TYPE_NPCM7XX_GCR, | |
259 | .parent = TYPE_SYS_BUS_DEVICE, | |
260 | .instance_size = sizeof(NPCM7xxGCRState), | |
261 | .instance_init = npcm7xx_gcr_init, | |
262 | .class_init = npcm7xx_gcr_class_init, | |
263 | }; | |
264 | ||
265 | static void npcm7xx_gcr_register_type(void) | |
266 | { | |
267 | type_register_static(&npcm7xx_gcr_info); | |
268 | } | |
269 | type_init(npcm7xx_gcr_register_type); |