1 // 2 // Copyright (c) 2010-2023 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Globalization; 9 using Antmicro.Renode.Core; 10 using Antmicro.Renode.Exceptions; 11 using Antmicro.Renode.Logging; 12 using Antmicro.Renode.Utilities; 13 using Antmicro.Renode.Peripherals.I2C; 14 using Antmicro.Renode.Core.Structure.Registers; 15 16 namespace Antmicro.Renode.Peripherals.Timers 17 { 18 public class RV8803_RTC : II2CPeripheral, IProvidesRegisterCollection<ByteRegisterCollection> 19 { RV8803_RTC(IMachine machine)20 public RV8803_RTC(IMachine machine) 21 { 22 IRQ = new GPIO(); 23 24 periodicTimer = new LimitTimer(machine.ClockSource, DefaultPeriodicTimerFrequency, this, "periodicTimer", limit: DefaultPeriodicTimerLimit, eventEnabled: true); 25 periodicTimer.LimitReached += () => 26 { 27 periodicTimerFlag.Value = true; 28 if(periodicTimerInterruptEnable.Value) 29 { 30 this.Log(LogLevel.Noisy, "IRQ blink"); 31 // yes, it blinks 32 IRQ.Blink(); 33 } 34 }; 35 36 realTimeCounter = new RTCTimer(machine, this); 37 38 RegistersCollection = new ByteRegisterCollection(this); 39 DefineRegisters(); 40 } 41 42 public string CurrentTime 43 { 44 get => realTimeCounter.CurrentTime.ToString(); 45 set 46 { 47 const string format = "dd-MM-yyyy HH:mm:ss"; 48 if(!DateTime.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt)) 49 { 50 throw new RecoverableException($"Couldn't parse time: {value}. Provide it in the {format} format (e.g., 31-12-2022 13:22:31)"); 51 } 52 realTimeCounter.CurrentTime = DateTimeWithCustomWeekday.FromDateTime(dt); 53 this.Log(LogLevel.Debug, "RTC time set to {0}", dt); 54 } 55 } 56 57 public GPIO IRQ { get; } 58 Reset()59 public void Reset() 60 { 61 realTimeCounter.Reset(); 62 periodicTimer.Reset(); 63 64 RegistersCollection.Reset(); 65 66 currentState = State.WaitingForRegister; 67 register = default(Registers); 68 69 IRQ.Unset(); 70 } 71 Write(byte[] data)72 public void Write(byte[] data) 73 { 74 this.Log(LogLevel.Debug, "Written {0} bytes: {1}", data.Length, Misc.PrettyPrintCollectionHex(data)); 75 foreach(var d in data) 76 { 77 HandleIncomingByte(d); 78 } 79 } 80 HandleIncomingByte(byte data)81 private void HandleIncomingByte(byte data) 82 { 83 switch(currentState) 84 { 85 case State.WaitingForRegister: 86 register = (Registers)data; 87 this.Log(LogLevel.Debug, "Register set to {0}", register); 88 89 currentState = State.HandlingData; 90 break; 91 92 case State.HandlingData: 93 HandleWrite(data); 94 break; 95 } 96 } 97 HandleWrite(byte data)98 private void HandleWrite(byte data) 99 { 100 this.Log(LogLevel.Debug, "Handling write of 0x{0:X} to {1}", data, register); 101 RegistersCollection.Write((long)register, data); 102 register++; 103 } 104 Read(int count = 1)105 public byte[] Read(int count = 1) 106 { 107 var result = new byte[count]; 108 for(var i = 0; i < count; i++) 109 { 110 result[i] = HandleRead(); 111 } 112 return result; 113 } 114 HandleRead()115 private byte HandleRead() 116 { 117 var result = RegistersCollection.Read((long)register); 118 this.Log(LogLevel.Debug, "Read value 0x{0:X} from {1}", result, register); 119 register++; 120 return result; 121 } 122 FinishTransmission()123 public void FinishTransmission() 124 { 125 this.Log(LogLevel.Debug, "Finished the transmission"); 126 currentState = State.WaitingForRegister; 127 } 128 129 public ByteRegisterCollection RegistersCollection { get; } 130 DefineRegisters()131 private void DefineRegisters() 132 { 133 Registers.TimerCounter0.Define(this) 134 .WithValueField(0, 8, name: "Timer Counter 0", 135 // When read, only the preset value is returned and not the actual value. 136 writeCallback: (_, newValue) => 137 { 138 var timerValue = periodicTimer.Value; 139 timerValue &= 0xF00; 140 timerValue |= newValue; 141 142 periodicTimer.Limit = DefaultPeriodicTimerLimit; 143 periodicTimer.Value = timerValue; 144 periodicTimer.Limit = timerValue; 145 }); 146 147 Registers.TimerCounter1.Define(this) 148 .WithValueField(0, 4, name: "Timer Counter 1", 149 // When read, only the preset value is returned and not the actual value. 150 writeCallback: (_, newValue) => 151 { 152 var timerValue = periodicTimer.Value; 153 timerValue &= 0x0FF; 154 timerValue |= (newValue << 8); 155 156 periodicTimer.Limit = DefaultPeriodicTimerLimit; 157 periodicTimer.Value = timerValue; 158 periodicTimer.Limit = timerValue; 159 }) 160 .WithFlag(4, name: "GP2 - General Purpose Bit") 161 .WithFlag(5, name: "GP3 - General Purpose Bit") 162 .WithFlag(6, name: "GP4 - General Purpose Bit") 163 .WithFlag(7, name: "GP5 - General Purpose Bit"); 164 165 var extensionRegister = new ByteRegister(this) 166 .WithTag("TD - Timer Clock Frequency", 0, 2) 167 .WithTag("FD - CLKOUT Frequency", 2, 2) 168 .WithFlag(4, out periodicCountdownTimerEnable, name: "TE - Periodic Countdown Timer Enable", 169 writeCallback: (_, val) => 170 { 171 UpdateTimersEnable(); 172 } 173 ) 174 .WithTag("USEL - Update Interrupt Select", 5, 1) 175 .WithTag("WADA - Weekday Alarm / Date Alarm Select", 6, 1) 176 .WithTag("TEST", 7, 1); 177 178 RegistersCollection.AddRegister((long)Registers.ExtensionRegister, extensionRegister); 179 RegistersCollection.AddRegister((long)Registers.ExtensionRegister_Extended1, extensionRegister); 180 181 var flagRegister = new ByteRegister(this) 182 .WithTag("V1F - Voltage Low Flag 1", 0, 1) 183 .WithTag("V2F - Voltage Low Flag 2", 1, 1) 184 .WithTag("EVF - External Event Flag", 2, 1) 185 .WithTag("AF - Alarm Flag", 3, 1) 186 .WithFlag(4, out periodicTimerFlag, name: "TF - Periodic Countdown Timer Flag") 187 .WithTag("UF - Periodic Time Update Flag", 5, 1) 188 .WithReservedBits(6, 2); 189 190 RegistersCollection.AddRegister((long)Registers.FlagRegister, flagRegister); 191 RegistersCollection.AddRegister((long)Registers.FlagRegister_Extended1, flagRegister); 192 193 var controlRegister = new ByteRegister(this) 194 .WithFlag(0, out reset, name: "RESET", 195 writeCallback: (_, val) => 196 { 197 UpdateTimersEnable(); 198 }) 199 .WithReservedBits(1, 1) 200 .WithTag("EIE - External Event Interrupt Enable", 2, 1) 201 .WithTag("AIE - Alarm Interrupt Enable", 3, 1) 202 .WithFlag(4, out periodicTimerInterruptEnable, name :"TIE - Periodic Countdown Timer Interrupt Enable") 203 .WithTag("UIE - Periodic Time Update Interrupt Enable", 5, 1) 204 .WithReservedBits(6, 2) 205 ; 206 207 RegistersCollection.AddRegister((long)Registers.ControlRegister, controlRegister); 208 RegistersCollection.AddRegister((long)Registers.ControlRegister_Extended1, controlRegister); 209 210 Registers.RAM.Define(this) 211 .WithValueField(0, 8, name: "RAM"); 212 213 var secondsRegister = new ByteRegister(this) 214 .WithValueField(0, 7, name: "Seconds", 215 valueProviderCallback: _ => BCDHelper.EncodeToBCD((byte)realTimeCounter.Second), 216 writeCallback: (_, value) => realTimeCounter.Second = BCDHelper.DecodeFromBCD((byte)value)) 217 .WithReservedBits(7, 1) 218 ; 219 220 RegistersCollection.AddRegister((long)Registers.Seconds, secondsRegister); 221 RegistersCollection.AddRegister((long)Registers.Seconds_Extended1, secondsRegister); 222 223 var minutesRegister = new ByteRegister(this) 224 .WithValueField(0, 7, name: "Minutes", 225 valueProviderCallback: _ => BCDHelper.EncodeToBCD((byte)realTimeCounter.Minute), 226 writeCallback: (_, value) => realTimeCounter.Minute = BCDHelper.DecodeFromBCD((byte)value)) 227 .WithReservedBits(7, 1) 228 ; 229 230 RegistersCollection.AddRegister((long)Registers.Minutes, minutesRegister); 231 RegistersCollection.AddRegister((long)Registers.Minutes_Extended1, minutesRegister); 232 233 var hoursRegister = new ByteRegister(this) 234 .WithValueField(0, 6, name: "Hours", 235 valueProviderCallback: _ => BCDHelper.EncodeToBCD((byte)realTimeCounter.Hour), 236 writeCallback: (_, value) => realTimeCounter.Hour = BCDHelper.DecodeFromBCD((byte)value)) 237 .WithReservedBits(6, 2) 238 ; 239 240 RegistersCollection.AddRegister((long)Registers.Hours, hoursRegister); 241 RegistersCollection.AddRegister((long)Registers.Hours_Extended1, hoursRegister); 242 243 var weekdayRegister = new ByteRegister(this) 244 .WithValueField(0, 7, name: "Weekday", 245 valueProviderCallback: _ => (uint)realTimeCounter.Weekday, 246 writeCallback: (_, value) => realTimeCounter.Weekday = (int)value) 247 .WithReservedBits(7, 1) 248 ; 249 250 RegistersCollection.AddRegister((long)Registers.Weekday, weekdayRegister); 251 RegistersCollection.AddRegister((long)Registers.Weekday_Extended1, weekdayRegister); 252 253 var dateRegister = new ByteRegister(this) 254 .WithValueField(0, 6, name: "Date", 255 valueProviderCallback: _ => BCDHelper.EncodeToBCD((byte)realTimeCounter.Day), 256 writeCallback: (_, value) => realTimeCounter.Day = BCDHelper.DecodeFromBCD((byte)value)) 257 .WithReservedBits(6, 2) 258 ; 259 260 RegistersCollection.AddRegister((long)Registers.Date, dateRegister); 261 RegistersCollection.AddRegister((long)Registers.Date_Extended1, dateRegister); 262 263 var monthRegister = new ByteRegister(this) 264 .WithValueField(0, 5, name: "Month", 265 valueProviderCallback: _ => BCDHelper.EncodeToBCD((byte)realTimeCounter.Month), 266 writeCallback: (_, value) => realTimeCounter.Month = BCDHelper.DecodeFromBCD((byte)value)) 267 .WithReservedBits(5, 3) 268 ; 269 270 RegistersCollection.AddRegister((long)Registers.Month, monthRegister); 271 RegistersCollection.AddRegister((long)Registers.Month_Extended1, monthRegister); 272 273 var yearRegister = new ByteRegister(this) 274 .WithValueField(0, 8, name: "Year", 275 valueProviderCallback: _ => BCDHelper.EncodeToBCD((byte)realTimeCounter.Year), 276 writeCallback: (_, value) => realTimeCounter.Year = BCDHelper.DecodeFromBCD((byte)value)) 277 ; 278 279 RegistersCollection.AddRegister((long)Registers.Year, yearRegister); 280 RegistersCollection.AddRegister((long)Registers.Year_Extended1, yearRegister); 281 } 282 UpdateTimersEnable()283 private void UpdateTimersEnable() 284 { 285 periodicTimer.Enabled = !reset.Value && periodicCountdownTimerEnable.Value; 286 realTimeCounter.Enabled = !reset.Value; 287 } 288 289 private IFlagRegisterField periodicCountdownTimerEnable; 290 private IFlagRegisterField reset; 291 private IFlagRegisterField periodicTimerFlag; 292 private IFlagRegisterField periodicTimerInterruptEnable; 293 294 private State currentState; 295 private Registers register; 296 297 private readonly LimitTimer periodicTimer; 298 private readonly RTCTimer realTimeCounter; 299 300 private const int DefaultPeriodicTimerFrequency = 4096; 301 private const int DefaultPeriodicTimerLimit = 4096; 302 303 private enum Registers 304 { 305 Seconds = 0x00, 306 Minutes = 0x01, 307 Hours = 0x02, 308 Weekday = 0x03, 309 Date = 0x04, 310 Month = 0x05, 311 Year = 0x06, 312 RAM = 0x07, 313 MinutesAlarm = 0x08, 314 HoursAlarm = 0x09, 315 WeekdayAlarm_DateAlarm = 0x0A, 316 TimerCounter0 = 0x0B, 317 TimerCounter1 = 0x0C, 318 ExtensionRegister = 0x0D, 319 FlagRegister = 0x0E, 320 ControlRegister = 0x0F, 321 322 Seconds100th_Extended1 = 0x10, 323 Seconds_Extended1 = 0x11, 324 Minutes_Extended1 = 0x12, 325 Hours_Extended1 = 0x13, 326 Weekday_Extended1 = 0x14, 327 Date_Extended1 = 0x15, 328 Month_Extended1 = 0x16, 329 Year_Extended1 = 0x17, 330 MinutesAlarm_Extended1 = 0x18, 331 HoursAlarm_Extended1 = 0x19, 332 WeekdayAlarm_DateAlarm_Extended1 = 0x1A, 333 TimerCounter0_Extended1 = 0x1B, 334 TimerCounter1_Extended1 = 0x1C, 335 ExtensionRegister_Extended1 = 0x1D, 336 FlagRegister_Extended1 = 0x1E, 337 ControlRegister_Extended1 = 0x1F, 338 339 Seconds100thCP_Extended2 = 0x20, 340 SecondsCP_Extended2 = 0x21, 341 Offset_Extended2 = 0x2C, 342 EventControl_Extended2 = 0x2F, 343 } 344 345 private enum State 346 { 347 WaitingForRegister, 348 HandlingData, 349 } 350 351 private enum Operation 352 { 353 Write = 0, 354 Read = 1 355 } 356 357 private class RTCTimer 358 { RTCTimer(IMachine machine, IPeripheral parent)359 public RTCTimer(IMachine machine, IPeripheral parent) 360 { 361 this.parent = parent; 362 363 innerTimer = new LimitTimer(machine.ClockSource, RTCFrequency, parent, "RTC timer", limit: RTCLimit); 364 innerTimer.LimitReached += () => 365 { 366 // this is called every second 367 currentTime.AddSeconds(1); 368 }; 369 370 Reset(); 371 } 372 Reset()373 public void Reset() 374 { 375 currentTime = new DateTimeWithCustomWeekday(); 376 innerTimer.Reset(); 377 } 378 379 public DateTimeWithCustomWeekday CurrentTime 380 { 381 get => currentTime; 382 set 383 { 384 currentTime = value; 385 } 386 } 387 388 public bool Enabled 389 { 390 get => innerTimer.Enabled; 391 set 392 { 393 innerTimer.Enabled = value; 394 } 395 } 396 397 public int Second 398 { 399 get => currentTime.Second; 400 set 401 { 402 try 403 { 404 currentTime.Second = value; 405 } 406 catch(ArgumentException e) 407 { 408 parent.Log(LogLevel.Warning, e.Message); 409 } 410 } 411 } 412 413 public int Minute 414 { 415 get => currentTime.Minute; 416 set 417 { 418 try 419 { 420 currentTime.Minute = value; 421 } 422 catch(ArgumentException e) 423 { 424 parent.Log(LogLevel.Warning, e.Message); 425 } 426 } 427 } 428 429 public int Hour 430 { 431 get => currentTime.Hour; 432 set 433 { 434 try 435 { 436 currentTime.Hour = value; 437 } 438 catch(ArgumentException e) 439 { 440 parent.Log(LogLevel.Warning, e.Message); 441 } 442 } 443 } 444 445 public int Day 446 { 447 get => currentTime.Day; 448 set 449 { 450 try 451 { 452 currentTime.Day = value; 453 } 454 catch(ArgumentException e) 455 { 456 parent.Log(LogLevel.Warning, e.Message); 457 } 458 } 459 } 460 461 public int Month 462 { 463 get => currentTime.Month; 464 set 465 { 466 try 467 { 468 currentTime.Month = value; 469 } 470 catch(ArgumentException e) 471 { 472 parent.Log(LogLevel.Warning, e.Message); 473 } 474 } 475 } 476 477 public int Year 478 { 479 get => currentTime.Year % 100; 480 set 481 { 482 if(value < 0 || value > 99) 483 { 484 parent.Log(LogLevel.Warning, "Year value out of range: {0}", value); 485 return; 486 } 487 488 currentTime.Year = 2000 + value; 489 } 490 } 491 492 // Encoded as a single bit at positions 0, 1, 2, 3, 4, 5 or 6 493 public int Weekday 494 { 495 get 496 { 497 var v = (int)currentTime.Weekday; 498 if(v < 1) 499 { 500 v += 7; 501 } 502 return 1 << (v - 1); 503 } 504 set 505 { 506 if(value <= 0) 507 { 508 parent.Log(LogLevel.Warning, "Weekday value (0x{0:X}) out of range, expected exactly one bit to be set", value); 509 return; 510 } 511 512 var originalValue = value; 513 var newWeekDay = 0; 514 while(!BitHelper.IsBitSet((uint)value, 0)) 515 { 516 newWeekDay++; 517 value >>= 1; 518 } 519 520 if(value != 0 || newWeekDay > 6) 521 { 522 parent.Log(LogLevel.Warning, "Weekday value (0x{0:X}) out of range, expected exactly one bit to be set", originalValue); 523 return; 524 } 525 526 currentTime.Weekday = (DayOfWeek)newWeekDay; 527 } 528 } 529 530 private DateTimeWithCustomWeekday currentTime; 531 532 private readonly IPeripheral parent; 533 private readonly LimitTimer innerTimer; 534 535 private const int RTCFrequency = 1; 536 private const int RTCLimit = 1; 537 } 538 } 539 } 540