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