allwinner-rtc.c (12977B)
1/* 2 * Allwinner Real Time Clock emulation 3 * 4 * Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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 "qemu-common.h" 27#include "hw/qdev-properties.h" 28#include "hw/rtc/allwinner-rtc.h" 29#include "trace.h" 30 31/* RTC registers */ 32enum { 33 REG_LOSC = 1, /* Low Oscillator Control */ 34 REG_YYMMDD, /* RTC Year-Month-Day */ 35 REG_HHMMSS, /* RTC Hour-Minute-Second */ 36 REG_ALARM1_WKHHMMSS, /* Alarm1 Week Hour-Minute-Second */ 37 REG_ALARM1_EN, /* Alarm1 Enable */ 38 REG_ALARM1_IRQ_EN, /* Alarm1 IRQ Enable */ 39 REG_ALARM1_IRQ_STA, /* Alarm1 IRQ Status */ 40 REG_GP0, /* General Purpose Register 0 */ 41 REG_GP1, /* General Purpose Register 1 */ 42 REG_GP2, /* General Purpose Register 2 */ 43 REG_GP3, /* General Purpose Register 3 */ 44 45 /* sun4i registers */ 46 REG_ALARM1_DDHHMMSS, /* Alarm1 Day Hour-Minute-Second */ 47 REG_CPUCFG, /* CPU Configuration Register */ 48 49 /* sun6i registers */ 50 REG_LOSC_AUTOSTA, /* LOSC Auto Switch Status */ 51 REG_INT_OSC_PRE, /* Internal OSC Clock Prescaler */ 52 REG_ALARM0_COUNTER, /* Alarm0 Counter */ 53 REG_ALARM0_CUR_VLU, /* Alarm0 Counter Current Value */ 54 REG_ALARM0_ENABLE, /* Alarm0 Enable */ 55 REG_ALARM0_IRQ_EN, /* Alarm0 IRQ Enable */ 56 REG_ALARM0_IRQ_STA, /* Alarm0 IRQ Status */ 57 REG_ALARM_CONFIG, /* Alarm Config */ 58 REG_LOSC_OUT_GATING, /* LOSC Output Gating Register */ 59 REG_GP4, /* General Purpose Register 4 */ 60 REG_GP5, /* General Purpose Register 5 */ 61 REG_GP6, /* General Purpose Register 6 */ 62 REG_GP7, /* General Purpose Register 7 */ 63 REG_RTC_DBG, /* RTC Debug Register */ 64 REG_GPL_HOLD_OUT, /* GPL Hold Output Register */ 65 REG_VDD_RTC, /* VDD RTC Regulate Register */ 66 REG_IC_CHARA, /* IC Characteristics Register */ 67}; 68 69/* RTC register flags */ 70enum { 71 REG_LOSC_YMD = (1 << 7), 72 REG_LOSC_HMS = (1 << 8), 73}; 74 75/* RTC sun4i register map (offset to name) */ 76const uint8_t allwinner_rtc_sun4i_regmap[] = { 77 [0x0000] = REG_LOSC, 78 [0x0004] = REG_YYMMDD, 79 [0x0008] = REG_HHMMSS, 80 [0x000C] = REG_ALARM1_DDHHMMSS, 81 [0x0010] = REG_ALARM1_WKHHMMSS, 82 [0x0014] = REG_ALARM1_EN, 83 [0x0018] = REG_ALARM1_IRQ_EN, 84 [0x001C] = REG_ALARM1_IRQ_STA, 85 [0x0020] = REG_GP0, 86 [0x0024] = REG_GP1, 87 [0x0028] = REG_GP2, 88 [0x002C] = REG_GP3, 89 [0x003C] = REG_CPUCFG, 90}; 91 92/* RTC sun6i register map (offset to name) */ 93const uint8_t allwinner_rtc_sun6i_regmap[] = { 94 [0x0000] = REG_LOSC, 95 [0x0004] = REG_LOSC_AUTOSTA, 96 [0x0008] = REG_INT_OSC_PRE, 97 [0x0010] = REG_YYMMDD, 98 [0x0014] = REG_HHMMSS, 99 [0x0020] = REG_ALARM0_COUNTER, 100 [0x0024] = REG_ALARM0_CUR_VLU, 101 [0x0028] = REG_ALARM0_ENABLE, 102 [0x002C] = REG_ALARM0_IRQ_EN, 103 [0x0030] = REG_ALARM0_IRQ_STA, 104 [0x0040] = REG_ALARM1_WKHHMMSS, 105 [0x0044] = REG_ALARM1_EN, 106 [0x0048] = REG_ALARM1_IRQ_EN, 107 [0x004C] = REG_ALARM1_IRQ_STA, 108 [0x0050] = REG_ALARM_CONFIG, 109 [0x0060] = REG_LOSC_OUT_GATING, 110 [0x0100] = REG_GP0, 111 [0x0104] = REG_GP1, 112 [0x0108] = REG_GP2, 113 [0x010C] = REG_GP3, 114 [0x0110] = REG_GP4, 115 [0x0114] = REG_GP5, 116 [0x0118] = REG_GP6, 117 [0x011C] = REG_GP7, 118 [0x0170] = REG_RTC_DBG, 119 [0x0180] = REG_GPL_HOLD_OUT, 120 [0x0190] = REG_VDD_RTC, 121 [0x01F0] = REG_IC_CHARA, 122}; 123 124static bool allwinner_rtc_sun4i_read(AwRtcState *s, uint32_t offset) 125{ 126 /* no sun4i specific registers currently implemented */ 127 return false; 128} 129 130static bool allwinner_rtc_sun4i_write(AwRtcState *s, uint32_t offset, 131 uint32_t data) 132{ 133 /* no sun4i specific registers currently implemented */ 134 return false; 135} 136 137static bool allwinner_rtc_sun6i_read(AwRtcState *s, uint32_t offset) 138{ 139 const AwRtcClass *c = AW_RTC_GET_CLASS(s); 140 141 switch (c->regmap[offset]) { 142 case REG_GP4: /* General Purpose Register 4 */ 143 case REG_GP5: /* General Purpose Register 5 */ 144 case REG_GP6: /* General Purpose Register 6 */ 145 case REG_GP7: /* General Purpose Register 7 */ 146 return true; 147 default: 148 break; 149 } 150 return false; 151} 152 153static bool allwinner_rtc_sun6i_write(AwRtcState *s, uint32_t offset, 154 uint32_t data) 155{ 156 const AwRtcClass *c = AW_RTC_GET_CLASS(s); 157 158 switch (c->regmap[offset]) { 159 case REG_GP4: /* General Purpose Register 4 */ 160 case REG_GP5: /* General Purpose Register 5 */ 161 case REG_GP6: /* General Purpose Register 6 */ 162 case REG_GP7: /* General Purpose Register 7 */ 163 return true; 164 default: 165 break; 166 } 167 return false; 168} 169 170static uint64_t allwinner_rtc_read(void *opaque, hwaddr offset, 171 unsigned size) 172{ 173 AwRtcState *s = AW_RTC(opaque); 174 const AwRtcClass *c = AW_RTC_GET_CLASS(s); 175 uint64_t val = 0; 176 177 if (offset >= c->regmap_size) { 178 qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", 179 __func__, (uint32_t)offset); 180 return 0; 181 } 182 183 if (!c->regmap[offset]) { 184 qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid register 0x%04x\n", 185 __func__, (uint32_t)offset); 186 return 0; 187 } 188 189 switch (c->regmap[offset]) { 190 case REG_LOSC: /* Low Oscillator Control */ 191 val = s->regs[REG_LOSC]; 192 s->regs[REG_LOSC] &= ~(REG_LOSC_YMD | REG_LOSC_HMS); 193 break; 194 case REG_YYMMDD: /* RTC Year-Month-Day */ 195 case REG_HHMMSS: /* RTC Hour-Minute-Second */ 196 case REG_GP0: /* General Purpose Register 0 */ 197 case REG_GP1: /* General Purpose Register 1 */ 198 case REG_GP2: /* General Purpose Register 2 */ 199 case REG_GP3: /* General Purpose Register 3 */ 200 val = s->regs[c->regmap[offset]]; 201 break; 202 default: 203 if (!c->read(s, offset)) { 204 qemu_log_mask(LOG_UNIMP, "%s: unimplemented register 0x%04x\n", 205 __func__, (uint32_t)offset); 206 } 207 val = s->regs[c->regmap[offset]]; 208 break; 209 } 210 211 trace_allwinner_rtc_read(offset, val); 212 return val; 213} 214 215static void allwinner_rtc_write(void *opaque, hwaddr offset, 216 uint64_t val, unsigned size) 217{ 218 AwRtcState *s = AW_RTC(opaque); 219 const AwRtcClass *c = AW_RTC_GET_CLASS(s); 220 221 if (offset >= c->regmap_size) { 222 qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", 223 __func__, (uint32_t)offset); 224 return; 225 } 226 227 if (!c->regmap[offset]) { 228 qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid register 0x%04x\n", 229 __func__, (uint32_t)offset); 230 return; 231 } 232 233 trace_allwinner_rtc_write(offset, val); 234 235 switch (c->regmap[offset]) { 236 case REG_YYMMDD: /* RTC Year-Month-Day */ 237 s->regs[REG_YYMMDD] = val; 238 s->regs[REG_LOSC] |= REG_LOSC_YMD; 239 break; 240 case REG_HHMMSS: /* RTC Hour-Minute-Second */ 241 s->regs[REG_HHMMSS] = val; 242 s->regs[REG_LOSC] |= REG_LOSC_HMS; 243 break; 244 case REG_GP0: /* General Purpose Register 0 */ 245 case REG_GP1: /* General Purpose Register 1 */ 246 case REG_GP2: /* General Purpose Register 2 */ 247 case REG_GP3: /* General Purpose Register 3 */ 248 s->regs[c->regmap[offset]] = val; 249 break; 250 default: 251 if (!c->write(s, offset, val)) { 252 qemu_log_mask(LOG_UNIMP, "%s: unimplemented register 0x%04x\n", 253 __func__, (uint32_t)offset); 254 } 255 break; 256 } 257} 258 259static const MemoryRegionOps allwinner_rtc_ops = { 260 .read = allwinner_rtc_read, 261 .write = allwinner_rtc_write, 262 .endianness = DEVICE_NATIVE_ENDIAN, 263 .valid = { 264 .min_access_size = 4, 265 .max_access_size = 4, 266 }, 267 .impl.min_access_size = 4, 268}; 269 270static void allwinner_rtc_reset(DeviceState *dev) 271{ 272 AwRtcState *s = AW_RTC(dev); 273 struct tm now; 274 275 /* Clear registers */ 276 memset(s->regs, 0, sizeof(s->regs)); 277 278 /* Get current datetime */ 279 qemu_get_timedate(&now, 0); 280 281 /* Set RTC with current datetime */ 282 if (s->base_year > 1900) { 283 s->regs[REG_YYMMDD] = ((now.tm_year + 1900 - s->base_year) << 16) | 284 ((now.tm_mon + 1) << 8) | 285 now.tm_mday; 286 s->regs[REG_HHMMSS] = (((now.tm_wday + 6) % 7) << 29) | 287 (now.tm_hour << 16) | 288 (now.tm_min << 8) | 289 now.tm_sec; 290 } 291} 292 293static void allwinner_rtc_init(Object *obj) 294{ 295 SysBusDevice *sbd = SYS_BUS_DEVICE(obj); 296 AwRtcState *s = AW_RTC(obj); 297 298 /* Memory mapping */ 299 memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_rtc_ops, s, 300 TYPE_AW_RTC, 1 * KiB); 301 sysbus_init_mmio(sbd, &s->iomem); 302} 303 304static const VMStateDescription allwinner_rtc_vmstate = { 305 .name = "allwinner-rtc", 306 .version_id = 1, 307 .minimum_version_id = 1, 308 .fields = (VMStateField[]) { 309 VMSTATE_UINT32_ARRAY(regs, AwRtcState, AW_RTC_REGS_NUM), 310 VMSTATE_END_OF_LIST() 311 } 312}; 313 314static Property allwinner_rtc_properties[] = { 315 DEFINE_PROP_INT32("base-year", AwRtcState, base_year, 0), 316 DEFINE_PROP_END_OF_LIST(), 317}; 318 319static void allwinner_rtc_class_init(ObjectClass *klass, void *data) 320{ 321 DeviceClass *dc = DEVICE_CLASS(klass); 322 323 dc->reset = allwinner_rtc_reset; 324 dc->vmsd = &allwinner_rtc_vmstate; 325 device_class_set_props(dc, allwinner_rtc_properties); 326} 327 328static void allwinner_rtc_sun4i_init(Object *obj) 329{ 330 AwRtcState *s = AW_RTC(obj); 331 s->base_year = 2010; 332} 333 334static void allwinner_rtc_sun4i_class_init(ObjectClass *klass, void *data) 335{ 336 AwRtcClass *arc = AW_RTC_CLASS(klass); 337 338 arc->regmap = allwinner_rtc_sun4i_regmap; 339 arc->regmap_size = sizeof(allwinner_rtc_sun4i_regmap); 340 arc->read = allwinner_rtc_sun4i_read; 341 arc->write = allwinner_rtc_sun4i_write; 342} 343 344static void allwinner_rtc_sun6i_init(Object *obj) 345{ 346 AwRtcState *s = AW_RTC(obj); 347 s->base_year = 1970; 348} 349 350static void allwinner_rtc_sun6i_class_init(ObjectClass *klass, void *data) 351{ 352 AwRtcClass *arc = AW_RTC_CLASS(klass); 353 354 arc->regmap = allwinner_rtc_sun6i_regmap; 355 arc->regmap_size = sizeof(allwinner_rtc_sun6i_regmap); 356 arc->read = allwinner_rtc_sun6i_read; 357 arc->write = allwinner_rtc_sun6i_write; 358} 359 360static void allwinner_rtc_sun7i_init(Object *obj) 361{ 362 AwRtcState *s = AW_RTC(obj); 363 s->base_year = 1970; 364} 365 366static void allwinner_rtc_sun7i_class_init(ObjectClass *klass, void *data) 367{ 368 AwRtcClass *arc = AW_RTC_CLASS(klass); 369 allwinner_rtc_sun4i_class_init(klass, arc); 370} 371 372static const TypeInfo allwinner_rtc_info = { 373 .name = TYPE_AW_RTC, 374 .parent = TYPE_SYS_BUS_DEVICE, 375 .instance_init = allwinner_rtc_init, 376 .instance_size = sizeof(AwRtcState), 377 .class_init = allwinner_rtc_class_init, 378 .class_size = sizeof(AwRtcClass), 379 .abstract = true, 380}; 381 382static const TypeInfo allwinner_rtc_sun4i_info = { 383 .name = TYPE_AW_RTC_SUN4I, 384 .parent = TYPE_AW_RTC, 385 .class_init = allwinner_rtc_sun4i_class_init, 386 .instance_init = allwinner_rtc_sun4i_init, 387}; 388 389static const TypeInfo allwinner_rtc_sun6i_info = { 390 .name = TYPE_AW_RTC_SUN6I, 391 .parent = TYPE_AW_RTC, 392 .class_init = allwinner_rtc_sun6i_class_init, 393 .instance_init = allwinner_rtc_sun6i_init, 394}; 395 396static const TypeInfo allwinner_rtc_sun7i_info = { 397 .name = TYPE_AW_RTC_SUN7I, 398 .parent = TYPE_AW_RTC, 399 .class_init = allwinner_rtc_sun7i_class_init, 400 .instance_init = allwinner_rtc_sun7i_init, 401}; 402 403static void allwinner_rtc_register(void) 404{ 405 type_register_static(&allwinner_rtc_info); 406 type_register_static(&allwinner_rtc_sun4i_info); 407 type_register_static(&allwinner_rtc_sun6i_info); 408 type_register_static(&allwinner_rtc_sun7i_info); 409} 410 411type_init(allwinner_rtc_register)