1 //
2 // Copyright (c) 2010-2025 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 
8 using System;
9 using Antmicro.Migrant;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Core.Structure.Registers;
12 using Antmicro.Renode.Logging;
13 
14 namespace Antmicro.Renode.Peripherals.Miscellaneous
15 {
16     public class SAM4S_DACC : BasicDoubleWordPeripheral, IKnownSize
17     {
SAM4S_DACC(IMachine machine, decimal referenceVoltage = 3.3m)18         public SAM4S_DACC(IMachine machine, decimal referenceVoltage = 3.3m) : base(machine)
19         {
20             ReferenceVoltage = referenceVoltage;
21             DefineRegisters();
22         }
23 
WriteDoubleWord(long offset, uint value)24         public override void WriteDoubleWord(long offset, uint value)
25         {
26             if(writeProtectionEnabled && Array.IndexOf(writeProtectedOffsets, offset) != -1)
27             {
28                 writeViolationStatus.Value = true;
29                 writeViolationSource.Value = (ulong)offset;
30                 this.Log(LogLevel.Warning, "Tried to write {0} while write protection is enabled", Enum.GetName(typeof(Registers), offset));
31                 return;
32             }
33 
34             base.WriteDoubleWord(offset, value);
35         }
36 
37         public long Size => 0x100;
38 
39         public GPIO IRQ { get; } = new GPIO();
40 
41         public decimal ReferenceVoltage { get; set; }
42 
43         public decimal MinimumVoltage => ReferenceVoltage / 6m;
44 
45         public decimal MaximumVoltage => 5m * ReferenceVoltage / 6m;
46 
47         public decimal VoltageRange => 4m * ReferenceVoltage / 6m;
48 
49         public decimal DAC0 => channelEnabled[0].Value ? ConvertVoltage(channelValue[0]) : 0m;
50 
51         public decimal DAC1 => channelEnabled[1].Value ? ConvertVoltage(channelValue[1]) : 0m;
52 
53         [field: Transient]
54         public event Action<int, decimal, ulong> OutputChanged;
55 
UpdateInterrupts()56         private void UpdateInterrupts()
57         {
58             var interrupt = false;
59 
60             interrupt |= transmitReadyInterruptEnabled.Value;
61             interrupt |= endOfConversionInterruptPending.Value && endOfConversionInterruptEnabled.Value;
62 
63             this.Log(LogLevel.Debug, "IRQ set to {0}", interrupt);
64             IRQ.Set(interrupt);
65         }
66 
ConvertVoltage(ulong value)67         private decimal ConvertVoltage(ulong value)
68         {
69             return MinimumVoltage + ((decimal)value / (decimal)MaximumChannelValue) * VoltageRange;
70         }
71 
PerformConversion(ulong data)72         private void PerformConversion(ulong data)
73         {
74             // NOTE: Split and truncate to 16 bits
75             var requests = wordMode.Value
76                 ? new ulong[] { data & 0xFFFF, (data >> 16) & 0xFFFF }
77                 : new ulong[] { data & 0xFFFF };
78 
79             foreach(var request in requests)
80             {
81                 var value = request & MaximumChannelValue;
82                 var channel = tagSelectionMode.Value ? (int)request >> 12 : (int)userSelectedChannel.Value;
83 
84                 if(channel >= NumberOfChannels)
85                 {
86                     this.Log(LogLevel.Warning, "Tried to set channel#{0}, but only {1} channels are available; ignoring", channel, NumberOfChannels);
87                     continue;
88                 }
89 
90                 if(channelEnabled[channel].Value && channelValue[channel] != value)
91                 {
92                     channelValue[channel] = value;
93                     OutputChanged?.Invoke(channel, ConvertVoltage(value), value);
94                 }
95             }
96 
97             endOfConversionInterruptPending.Value = true;
98             UpdateInterrupts();
99         }
100 
DefineRegisters()101         private void DefineRegisters()
102         {
103             Registers.Control.Define(this)
104                 .WithFlag(0, FieldMode.Write, name: "SWRST",
105                     writeCallback: (_, value) => { if(value) Reset(); })
106                 .WithReservedBits(1, 31)
107             ;
108 
109             Registers.Mode.Define(this)
110                 .WithTaggedFlag("TRGEN", 0)
111                 .WithTag("TRGSEL", 1, 3)
112                 .WithFlag(4, out wordMode, name: "WORD")
113                 .WithReservedBits(5, 3)
114                 .WithFlag(8, name: "ONE",
115                     valueProviderCallback: _ => true)
116                 .WithReservedBits(9, 7)
117                 .WithValueField(16, 2, out userSelectedChannel, name: "USER_SEL")
118                 .WithReservedBits(18, 2)
119                 .WithFlag(20, out tagSelectionMode, name: "TAG")
120                 .WithTaggedFlag("MAXS", 21)
121                 .WithReservedBits(22, 2)
122                 .WithTag("STARTUP", 24, 6)
123                 .WithReservedBits(30, 2)
124             ;
125 
126             Registers.ChannelEnable.Define(this)
127                 .WithFlags(0, 2, FieldMode.Set, name: "CH",
128                     writeCallback: (index, _, value) => { if(value) channelEnabled[index].Value = true; })
129                 .WithReservedBits(2, 30)
130             ;
131 
132             Registers.ChannelDisable.Define(this)
133                 .WithFlags(0, 2, FieldMode.WriteToClear, name: "CH",
134                     writeCallback: (index, _, value) =>
135                     {
136                         if(!value || !channelEnabled[index].Value)
137                         {
138                             return;
139                         }
140 
141                         channelEnabled[index].Value = false;
142                         // NOTE: There is no analog output when channel is disabled
143                         OutputChanged?.Invoke(index, 0, 0);
144                     })
145                 .WithReservedBits(2, 30)
146             ;
147 
148             Registers.ChannelStatus.Define(this)
149                 .WithFlags(0, 2, out channelEnabled, FieldMode.Read, name: "CH")
150                 .WithReservedBits(2, 30)
151             ;
152 
153             Registers.ConversionData.Define(this)
154                 .WithValueField(0, 32, FieldMode.Write, name: "DATA",
155                     writeCallback: (_, value) => PerformConversion(value))
156             ;
157 
158             Registers.InterruptEnable.Define(this)
159                 .WithFlag(0, FieldMode.Set, name: "TXRDY",
160                     writeCallback: (_, value) => { if(value) transmitReadyInterruptEnabled.Value = true; })
161                 .WithFlag(1, FieldMode.Set, name: "EOC",
162                     writeCallback: (_, value) => { if(value) endOfConversionInterruptEnabled.Value = true; })
163                 .WithTaggedFlag("ENDTX", 2)
164                 .WithTaggedFlag("TXBUFE", 3)
165                 .WithReservedBits(4, 28)
166                 .WithWriteCallback((_, __) => UpdateInterrupts())
167             ;
168 
169             Registers.InterruptDisable.Define(this)
170                 .WithFlag(0, FieldMode.WriteToClear, name: "TXRDY",
171                     writeCallback: (_, value) => { if(value) transmitReadyInterruptEnabled.Value = false; })
172                 .WithFlag(1, FieldMode.WriteToClear, name: "EOC",
173                     writeCallback: (_, value) => { if(value) endOfConversionInterruptEnabled.Value = false; })
174                 .WithTaggedFlag("ENDTX", 2)
175                 .WithTaggedFlag("TXBUFE", 3)
176                 .WithReservedBits(4, 28)
177                 .WithWriteCallback((_, __) => UpdateInterrupts())
178             ;
179 
180             Registers.InterruptMask.Define(this)
181                 .WithFlag(0, out transmitReadyInterruptEnabled, FieldMode.Read, name: "TXRDY")
182                 .WithFlag(1, out endOfConversionInterruptEnabled, FieldMode.Read, name: "EOC")
183                 .WithTaggedFlag("ENDTX", 2)
184                 .WithTaggedFlag("TXBUFE", 3)
185                 .WithReservedBits(4, 28)
186             ;
187 
188             Registers.InterruptStatus.Define(this)
189                 .WithFlag(0, FieldMode.Read, name: "TXRDY",
190                     valueProviderCallback: _ => true)
191                 .WithFlag(1, out endOfConversionInterruptPending, FieldMode.ReadToClear, name: "EOC")
192                 .WithTaggedFlag("ENDTX", 2)
193                 .WithTaggedFlag("TXBUFE", 3)
194                 .WithReservedBits(4, 28)
195                 .WithReadCallback((_, __) => UpdateInterrupts())
196             ;
197 
198             Registers.AnalogCurrent.Define(this)
199                 .WithTag("IBCTLCH0", 0, 2)
200                 .WithTag("IBCTLCH1", 2, 2)
201                 .WithReservedBits(4, 4)
202                 .WithTag("IBCTLDACCORE", 8, 2)
203                 .WithReservedBits(10, 22)
204             ;
205 
206             Registers.WriteProtectionMode.Define(this)
207                 .WithFlag(0, out var enableWriteProtection, FieldMode.Write, name: "WPEN")
208                 .WithReservedBits(1, 7)
209                 .WithValueField(8, 24, out var writeProtectionKey, FieldMode.Write, name: "WPKEY")
210                 .WithWriteCallback((_, __) =>
211                 {
212                     if(writeProtectionKey.Value != WriteProtectionKey)
213                     {
214                         return;
215                     }
216                     writeProtectionEnabled = enableWriteProtection.Value;
217                 })
218             ;
219 
220             Registers.WriteProtectionStatus.Define(this)
221                 .WithFlag(0, out writeViolationStatus, FieldMode.ReadToClear, name: "WPVS")
222                 .WithReservedBits(1, 7)
223                 .WithValueField(8, 8, out writeViolationSource, FieldMode.ReadToClear, name: "WPVSRC")
224                 .WithReservedBits(16, 16)
225             ;
226         }
227 
228         private bool writeProtectionEnabled;
229 
230         private IFlagRegisterField wordMode;
231         private IValueRegisterField userSelectedChannel;
232         private IFlagRegisterField tagSelectionMode;
233 
234         private IFlagRegisterField[] channelEnabled;
235 
236         private IFlagRegisterField transmitReadyInterruptEnabled;
237         private IFlagRegisterField endOfConversionInterruptEnabled;
238 
239         private IFlagRegisterField endOfConversionInterruptPending;
240 
241         private IFlagRegisterField writeViolationStatus;
242         private IValueRegisterField writeViolationSource;
243 
244         private readonly ulong[] writeProtectedOffsets = new ulong[]
245         {
246             (ulong)Registers.Mode,
247             (ulong)Registers.ChannelEnable,
248             (ulong)Registers.ChannelDisable,
249             (ulong)Registers.AnalogCurrent,
250         };
251 
252         private readonly ulong[] channelValue = new ulong[NumberOfChannels];
253 
254         private const int NumberOfChannels = 2;
255         private const uint WriteProtectionKey = 0x444143;
256         private const uint MaximumChannelValue = 0xFFF;
257 
258         public enum Registers
259         {
260             Control = 0x00,
261             Mode = 0x04,
262             ChannelEnable = 0x10,
263             ChannelDisable = 0x14,
264             ChannelStatus = 0x18,
265             ConversionData = 0x20,
266             InterruptEnable = 0x24,
267             InterruptDisable = 0x28,
268             InterruptMask = 0x2C,
269             InterruptStatus = 0x30,
270             AnalogCurrent = 0x94,
271             WriteProtectionMode = 0xE4,
272             WriteProtectionStatus = 0xE8
273         }
274     }
275 }
276