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