1 //
2 // Copyright (c) 2010-2023 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.Peripherals.Bus;
10 using Antmicro.Renode.Logging;
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.Core.Structure.Registers;
13 using Antmicro.Renode.Time;
14 using System.Collections.Generic;
15 using System.Collections.ObjectModel;
16 
17 namespace Antmicro.Renode.Peripherals.Timers
18 {
19     public sealed class SunxiHighSpeedTimer : IDoubleWordPeripheral, IKnownSize, INumberedGPIOOutput
20     {
SunxiHighSpeedTimer(IMachine machine, long frequency)21         public SunxiHighSpeedTimer(IMachine machine, long frequency)
22         {
23             irqEnableRegister = new DoubleWordRegister(this);
24             irqStatusRegister = new DoubleWordRegister(this);
25 
26             timers = new SunxiHighSpeedTimerUnit[4];
27             interruptFlags = new IFlagRegisterField[4];
28             enableFlags = new IFlagRegisterField[4];
29 
30             for(var i = 0; i < 4; ++i)
31             {
32                 var j = i;
33                 timers[i] = new SunxiHighSpeedTimerUnit(machine, frequency);
34                 timers[i].LimitReached += () => OnTimerLimitReached(j);
35                 interruptFlags[i] = irqStatusRegister.DefineFlagField(i, FieldMode.WriteOneToClear, name: "Tx_IRQ_PEND");
36                 enableFlags[i] = irqEnableRegister.DefineFlagField(i, name: "Tx_INT_EN");
37             }
38 
39             var innerConnections = new Dictionary<int, IGPIO>();
40             for(var i = 0; i < 4; ++i)
41             {
42                 innerConnections[i] = new GPIO();
43             }
44             Connections = new ReadOnlyDictionary<int, IGPIO>(innerConnections);
45         }
46 
ReadDoubleWord(long offset)47         public uint ReadDoubleWord(long offset)
48         {
49             SunxiHighSpeedTimerUnit timerUnit;
50             var offsetPerTimer = (Registers)GetTimerRegisterByOffset(offset, out timerUnit);
51             switch(offsetPerTimer)
52             {
53             case Registers.IRQEnable:
54                 return irqEnableRegister.Read();
55             case Registers.IRQStatus:
56                 return irqStatusRegister.Read();
57             case Registers.Control:
58                 return timerUnit.ControlRegister;
59             case Registers.CurrentValueHigh:
60                 return timerUnit.ValueRegisterHigh;
61             case Registers.CurrentValueLow:
62                 return timerUnit.ValueRegisterLow;
63             case Registers.IntervalHigh:
64                 return timerUnit.IntervalRegisterHigh;
65             case Registers.IntervalLow:
66                 return timerUnit.IntervalRegisterLow;
67             default:
68                 this.LogUnhandledRead(offset);
69                 return 0;
70             }
71         }
72 
WriteDoubleWord(long offset, uint value)73         public void WriteDoubleWord(long offset, uint value)
74         {
75             SunxiHighSpeedTimerUnit timerUnit;
76             var offsetPerTimer = (Registers)GetTimerRegisterByOffset(offset, out timerUnit);
77             switch(offsetPerTimer)
78             {
79             case Registers.IRQEnable:
80                 irqEnableRegister.Write(offset, value);
81                 Update();
82                 break;
83             case Registers.IRQStatus:
84                 irqStatusRegister.Write(offset, value);
85                 Update();
86                 break;
87             case Registers.Control:
88                 timerUnit.ControlRegister = value;
89                 break;
90             case Registers.CurrentValueHigh:
91                 timerUnit.ValueRegisterHigh = value;
92                 break;
93             case Registers.CurrentValueLow:
94                 timerUnit.ValueRegisterLow = value;
95                 break;
96             case Registers.IntervalHigh:
97                 timerUnit.IntervalRegisterHigh = value;
98                 break;
99             case Registers.IntervalLow:
100                 timerUnit.IntervalRegisterLow = value;
101                 break;
102             default:
103                 this.LogUnhandledWrite(offset, value);
104                 break;
105             }
106         }
107 
Reset()108         public void Reset()
109         {
110             for(var i = 0; i < 4; ++i)
111             {
112                 timers[i].Reset();
113             }
114             irqEnableRegister.Reset();
115             irqStatusRegister.Reset();
116         }
117 
118         public long Size
119         {
120             get
121             {
122                 return 0x2000;
123             }
124         }
125 
126         public IReadOnlyDictionary<int, IGPIO> Connections { get; private set; }
127 
GetTimerRegisterByOffset(long offset, out SunxiHighSpeedTimerUnit timer)128         private int GetTimerRegisterByOffset(long offset, out SunxiHighSpeedTimerUnit timer)
129         {
130             if(offset < 0x10)
131             {
132                 timer = null;
133                 return (int)offset;
134             }
135 
136             var offsetPerTimer = ((offset - 0x10) % 0x20) + 0x10;
137             var timerNumber = (uint)(offset - 0x10) / 0x20;
138             timer = timers[timerNumber];
139             return (int)offsetPerTimer;
140         }
141 
OnTimerLimitReached(int timerId)142         private void OnTimerLimitReached(int timerId)
143         {
144             this.Log(LogLevel.Noisy, "HSTimer {0} limit reached.", timerId);
145             if(enableFlags[timerId].Value)
146             {
147                 interruptFlags[timerId].Value = true;
148                 Update();
149             }
150         }
151 
Update()152         private void Update()
153         {
154             for(var i = 0; i < 4; ++i)
155             {
156                 if(enableFlags[i].Value && interruptFlags[i].Value)
157                 {
158                     Connections[i].Set();
159                 }
160                 else
161                 {
162                     Connections[i].Unset();
163                 }
164             }
165         }
166 
167         private readonly SunxiHighSpeedTimerUnit[] timers;
168         private readonly IFlagRegisterField[] interruptFlags, enableFlags;
169         private readonly DoubleWordRegister irqEnableRegister, irqStatusRegister;
170 
171         private enum Registers
172         {
173             IRQEnable = 0x00,
174             IRQStatus = 0x04,
175             Control = 0x10,
176             IntervalLow = 0x14,
177             IntervalHigh = 0x18,
178             CurrentValueLow = 0x1c,
179             CurrentValueHigh = 0x20
180         }
181 
182         private sealed class SunxiHighSpeedTimerUnit : LimitTimer
183         {
SunxiHighSpeedTimerUnit(IMachine machine, long frequency)184             public SunxiHighSpeedTimerUnit(IMachine machine, long frequency) : base(machine.ClockSource, frequency, direction: Direction.Descending, enabled: false)
185             {
186                 controlRegister = new DoubleWordRegister(this);
187                 controlRegister.DefineFlagField(7, changeCallback: OnModeChange, name: "MODE");
188                 controlRegister.DefineValueField(4, 3, writeCallback: (prevVal, val) => OnPrescalerChange((uint)prevVal, (uint)val), name: "PRESC");
189                 controlRegister.DefineFlagField(1, writeCallback: OnReload, name: "RELOAD");
190                 controlRegister.DefineFlagField(0, writeCallback: OnEnableChange, name: "ENABLE");
191                 EventEnabled = true;
192             }
193 
Reset()194             public override void Reset()
195             {
196                 base.Reset();
197                 controlRegister.Reset();
198             }
199 
200             public uint IntervalRegisterLow
201             {
202                 get
203                 {
204                     var currentLimt = Limit;
205                     savedIntervalHigh = (uint)(currentLimt >> 32) & 0x00ffffff;
206                     return (uint)currentLimt;
207                 }
208                 set
209                 {
210                     intervalRegisterLow = value;
211                 }
212             }
213 
214             public uint IntervalRegisterHigh
215             {
216                 get
217                 {
218                     return savedIntervalHigh;
219                 }
220                 set
221                 {
222                     Limit = ((ulong)value << 32) | intervalRegisterLow;
223                 }
224             }
225 
226             public uint ValueRegisterLow
227             {
228                 get
229                 {
230                     var currentValue = Value;
231                     savedValueHigh = (uint)(currentValue >> 32) & 0x00ffffff;
232                     return (uint)currentValue;
233                 }
234                 set
235                 {
236                     valueRegisterLow = value;
237                 }
238             }
239 
240             public uint ValueRegisterHigh
241             {
242                 get
243                 {
244                     return savedValueHigh;
245                 }
246                 set
247                 {
248                     Value = ((ulong)value << 32) | valueRegisterLow;
249                 }
250             }
251 
252             public uint ControlRegister
253             {
254                 get
255                 {
256                     return controlRegister.Read();
257                 }
258                 set
259                 {
260                     controlRegister.Write((long)Registers.Control, value);
261                 }
262             }
263 
264             // THIS IS A WORKAROUND FOR A BUG IN MONO
265             // https://bugzilla.xamarin.com/show_bug.cgi?id=39444
OnLimitReached()266             protected override void OnLimitReached()
267             {
268                 base.OnLimitReached();
269             }
270 
OnModeChange(bool oldValue, bool newValue)271             private void OnModeChange(bool oldValue, bool newValue)
272             {
273                 Mode = newValue ? WorkMode.OneShot : WorkMode.Periodic;
274             }
275 
OnPrescalerChange(ulong oldValue, ulong newValue)276             private void OnPrescalerChange(ulong oldValue, ulong newValue)
277             {
278                 if(newValue < 4)
279                 {
280                     Divider = 1 << (int)newValue;
281                 }
282                 else
283                 {
284                     this.Log(LogLevel.Warning, "Invalid prescaler value: {0}.", newValue);
285                 }
286             }
287 
OnReload(bool oldValue, bool newValue)288             private void OnReload(bool oldValue, bool newValue)
289             {
290                 if(newValue)
291                 {
292                     Value = Limit;
293                 }
294             }
295 
OnEnableChange(bool oldValue, bool newValue)296             private void OnEnableChange(bool oldValue, bool newValue)
297             {
298                 Enabled = newValue;
299             }
300 
301             private uint savedIntervalHigh, savedValueHigh, intervalRegisterLow, valueRegisterLow;
302             private readonly DoubleWordRegister controlRegister;
303         }
304     }
305 }
306 
307