1 // 2 // Copyright (c) 2010-2018 Antmicro 3 // Copyright (c) 2011-2015 Realtime Embedded 4 // 5 // This file is licensed under the MIT License. 6 // Full license text is available in 'licenses/MIT.txt'. 7 // 8 using System; 9 using Antmicro.Renode.Core; 10 using Antmicro.Renode.Core.Structure.Registers; 11 using Antmicro.Renode.Logging; 12 using Antmicro.Renode.Peripherals.Bus; 13 using Antmicro.Renode.Time; 14 15 namespace Antmicro.Renode.Peripherals.Timers 16 { 17 public sealed class SunxiTimer : IDoubleWordPeripheral, IKnownSize 18 { SunxiTimer(IMachine machine)19 public SunxiTimer(IMachine machine) 20 { 21 timers = new SunxiTimerUnit[NumberOfTimerUnits]; 22 for(int i = 0; i < NumberOfTimerUnits; ++i) 23 { 24 int j = i; 25 timers[i] = new SunxiTimerUnit(machine, this); 26 timers[i].LimitReached += () => OnTimerLimitReached(j); 27 } 28 timerInterruptEnabled = new IFlagRegisterField[NumberOfTimerUnits]; 29 timerInterruptStatus = new IFlagRegisterField[NumberOfTimerUnits]; 30 Timer0Irq = new GPIO(); 31 Timer1Irq = new GPIO(); 32 SetupRegisters(); 33 } 34 Reset()35 public void Reset() 36 { 37 foreach(var timer in timers) 38 { 39 timer.Reset(); 40 } 41 lowOscillatorControlRegister.Reset(); 42 timerIrqEnableRegister.Reset(); 43 timerStatusRegister.Reset(); 44 Update(); 45 } 46 ReadDoubleWord(long offset)47 public uint ReadDoubleWord(long offset) 48 { 49 if(offset >= FirstTimerOffset && offset < LastTimerOffset + TimerUnitSize) 50 { 51 var timerId = offset / TimerUnitSize - 1; 52 var timerOffset = offset % TimerUnitSize; 53 54 // Timers 2-5 are not used by the kernel and have a different structure than timers 0-1, so we didn't implement them. 55 if(timerId > 1) 56 { 57 this.Log(LogLevel.Warning, "Attempted to read from {1} register of Sunxi timer {2}, which is not implemented.", (Registers)timerOffset, timerId); 58 return 0; 59 } 60 61 switch((Registers)timerOffset) 62 { 63 case Registers.TimerXControl: 64 return timers[timerId].ControlRegister; 65 case Registers.TimerXCurrentValue: 66 return (uint)timers[timerId].Value; 67 case Registers.TimerXIntervalValue: 68 return (uint)timers[timerId].Limit; 69 default: 70 this.LogUnhandledRead(offset); 71 return 0; 72 } 73 } 74 else 75 { 76 switch((Registers)offset) 77 { 78 case Registers.TimerIrqEnable: 79 return timerIrqEnableRegister.Read(); 80 case Registers.TimerStatus: 81 return timerStatusRegister.Read(); 82 case Registers.LowOscillatorControl: 83 return lowOscillatorControlRegister.Read(); 84 default: 85 this.LogUnhandledRead(offset); 86 return 0; 87 } 88 } 89 } 90 WriteDoubleWord(long offset, uint value)91 public void WriteDoubleWord(long offset, uint value) 92 { 93 if(offset >= FirstTimerOffset && offset < LastTimerOffset + TimerUnitSize) 94 { 95 var timerId = offset / TimerUnitSize - 1; 96 var timerOffset = offset % TimerUnitSize; 97 98 // Timers 2-5 are not used by the kernel and have a different structure than timers 0-1, so we didn't implement them. 99 if(timerId > 1) 100 { 101 this.Log(LogLevel.Warning, "Attempted to write 0x{0:x} to {1} register of Sunxi timer {2}, which is not implemented.", value, (Registers)timerOffset, timerId); 102 return; 103 } 104 105 switch((Registers)timerOffset) 106 { 107 case Registers.TimerXControl: 108 timers[timerId].ControlRegister = value; 109 break; 110 case Registers.TimerXCurrentValue: 111 timers[timerId].Value = value; 112 break; 113 case Registers.TimerXIntervalValue: 114 timers[timerId].Limit = value; 115 break; 116 default: 117 this.LogUnhandledWrite(offset, value); 118 break; 119 } 120 } 121 else 122 { 123 switch((Registers)offset) 124 { 125 case Registers.TimerIrqEnable: 126 timerIrqEnableRegister.Write(offset, value); 127 break; 128 case Registers.TimerStatus: 129 timerStatusRegister.Write(offset, value); 130 Update(); 131 break; 132 case Registers.LowOscillatorControl: 133 lowOscillatorControlRegister.Write(offset, value); 134 break; 135 default: 136 this.LogUnhandledWrite(offset, value); 137 break; 138 } 139 } 140 } 141 142 public long Size 143 { 144 get 145 { 146 return 0x400; 147 } 148 } 149 150 public GPIO Timer0Irq 151 { 152 get; 153 private set; 154 } 155 156 public GPIO Timer1Irq 157 { 158 get; 159 private set; 160 } 161 SetupRegisters()162 private void SetupRegisters() 163 { 164 timerIrqEnableRegister = new DoubleWordRegister(this); 165 timerStatusRegister = new DoubleWordRegister(this); 166 for(int i = 0; i < NumberOfTimerUnits; ++i) 167 { 168 timerInterruptEnabled[i] = timerIrqEnableRegister.DefineFlagField(i); 169 timerInterruptStatus[i] = timerStatusRegister.DefineFlagField(i, FieldMode.WriteOneToClear | FieldMode.Read); 170 } 171 lowOscillatorControlRegister = new DoubleWordRegister(this, 0x4000); 172 lowOscillatorControlRegister.DefineFlagField(0, changeCallback: (oldValue, newValue) => lowOscillatorFrequency = newValue ? 32768 : 32000); 173 } 174 OnTimerLimitReached(int timerId)175 private void OnTimerLimitReached(int timerId) 176 { 177 if(timerInterruptEnabled[timerId].Value) 178 { 179 timerInterruptStatus[timerId].Value = true; 180 Update(); 181 } 182 } 183 Update()184 private void Update() 185 { 186 if(timerInterruptStatus[0].Value) 187 { 188 Timer0Irq.Set(); 189 } 190 else 191 { 192 Timer0Irq.Unset(); 193 } 194 195 if(timerInterruptStatus[1].Value) 196 { 197 Timer1Irq.Set(); 198 } 199 else 200 { 201 Timer1Irq.Unset(); 202 } 203 } 204 205 private SunxiTimerUnit[] timers; 206 private DoubleWordRegister timerIrqEnableRegister, timerStatusRegister, lowOscillatorControlRegister; 207 private IFlagRegisterField[] timerInterruptEnabled, timerInterruptStatus; 208 private long lowOscillatorFrequency; 209 210 private const int NumberOfTimerUnits = 2, TimerUnitSize = 0x10, FirstTimerOffset = 0x10, LastTimerOffset = 0x60; 211 212 private sealed class SunxiTimerUnit : LimitTimer 213 { SunxiTimerUnit(IMachine machine, SunxiTimer parent)214 public SunxiTimerUnit(IMachine machine, SunxiTimer parent) : base(machine.ClockSource, 24000000, direction: Antmicro.Renode.Time.Direction.Descending, enabled: false, eventEnabled: true) 215 { 216 timerGroup = parent; 217 controlRegister = new DoubleWordRegister(this, 0x04); 218 controlRegister.DefineFlagField(7, changeCallback: (oldValue, newValue) => Mode = newValue ? WorkMode.OneShot : WorkMode.Periodic); 219 controlRegister.DefineValueField(4, 3, changeCallback: (oldValue, newValue) => Divider = 1 << (int)newValue); 220 controlRegister.DefineFlagField(1, FieldMode.WriteOneToClear, writeCallback: (oldValue, newValue) => Value = Limit); 221 controlRegister.DefineFlagField(0, changeCallback: (oldValue, newValue) => Enabled = newValue); 222 controlRegister.DefineEnumField<ClockSource>(2, 2, changeCallback: OnClockSourceChange); 223 } 224 225 public uint ControlRegister 226 { 227 get 228 { 229 return controlRegister.Read(); 230 } 231 set 232 { 233 controlRegister.Write((long)Registers.TimerXControl, value); 234 } 235 } 236 237 // THIS IS A WORKAROUND FOR A BUG IN MONO 238 // https://bugzilla.xamarin.com/show_bug.cgi?id=39444 OnLimitReached()239 protected override void OnLimitReached() 240 { 241 base.OnLimitReached(); 242 } 243 OnClockSourceChange(ClockSource oldValue, ClockSource newValue)244 private void OnClockSourceChange(ClockSource oldValue, ClockSource newValue) 245 { 246 switch(newValue) 247 { 248 case ClockSource.LowSpeedOscillator: 249 Frequency = timerGroup.lowOscillatorFrequency; 250 break; 251 case ClockSource.Osc24M: 252 Frequency = 24000000; 253 break; 254 case ClockSource.Pll6: 255 Frequency = 200000000; 256 break; 257 default: 258 this.Log(LogLevel.Warning, "Invalid clock source value."); 259 break; 260 } 261 } 262 263 private readonly DoubleWordRegister controlRegister; 264 private readonly SunxiTimer timerGroup; 265 266 private enum ClockSource 267 { 268 LowSpeedOscillator = 0x00, 269 Osc24M = 0x01, 270 Pll6 = 0x10 271 } 272 } 273 274 private enum Registers 275 { 276 TimerIrqEnable = 0x00, 277 TimerStatus = 0x4, 278 TimerXControl = 0x00, 279 TimerXIntervalValue = 0x04, 280 TimerXCurrentValue = 0x08, 281 AvsControl = 0x80, 282 AvsCounter0 = 0x84, 283 AvsCounter1 = 0x88, 284 AvsDivisor = 0x8c, 285 WatchdogControl = 0x90, 286 WatchdogMode = 0x94, 287 LowOscillatorControl = 0x100, 288 RtcYearMonthDay = 0x104, 289 RtcHourMinuteSecond = 0x108, 290 AlarmDayHourMinuteSecond = 0x10c, 291 AlarmWeekHourMinuteSecond = 0x110, 292 AlarmEnable = 0x114, 293 AlarmIrqEnable = 0x118, 294 AlarmIrqStatus = 0x11c, 295 AlarmConfig = 0x170 296 } 297 } 298 } 299