1 //
2 // Copyright (c) 2010-2022 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 Antmicro.Renode.Core;
9 using Antmicro.Renode.Core.Structure.Registers;
10 using Antmicro.Renode.Logging;
11 
12 namespace Antmicro.Renode.Peripherals.Analog
13 {
14     public class MAX32650_ADC : BasicDoubleWordPeripheral, IKnownSize
15     {
MAX32650_ADC(IMachine machine)16         public MAX32650_ADC(IMachine machine) : base(machine)
17         {
18             IRQ = new GPIO();
19             DefineRegisters();
20         }
21 
Reset()22         public override void Reset()
23         {
24             base.Reset();
25             IRQ.Unset();
26         }
27 
28         public GPIO IRQ { get; }
29 
30         public long Size => 0x1000;
31 
32         public uint AIn0
33         {
34             get => aIn0;
35             set => aIn0 = ValidateAIn(0, value);
36         }
37 
38         public uint AIn1
39         {
40             get => aIn1;
41             set => aIn1 = ValidateAIn(1, value);
42         }
43 
44         public uint AIn2
45         {
46             get => aIn2;
47             set => aIn2 = ValidateAIn(2, value);
48         }
49 
50         public uint AIn3
51         {
52             get => aIn3;
53             set => aIn3 = ValidateAIn(3, value);
54         }
55 
GetValueFromActiveChannel()56         private uint GetValueFromActiveChannel()
57         {
58             switch(currentChannel.Value)
59             {
60                 case Channels.AIn0:
61                     return AIn0;
62                 case Channels.AIn1:
63                     return AIn1;
64                 case Channels.AIn2:
65                     return AIn2;
66                 case Channels.AIn3:
67                     return AIn3;
68                 case Channels.AIn0Div5:
69                     return AIn0 / 5;
70                 case Channels.AIn1Div5:
71                     return AIn1 / 5;
72                 default:
73                     this.Log(LogLevel.Warning, "{0} is not supported; ignoring", currentChannel.Value);
74                     return 0x00;
75             }
76         }
77 
ValidateAIn(int idx, uint value)78         private uint ValidateAIn(int idx, uint value)
79         {
80             if(value > ADCDataMask)
81             {
82                 this.Log(LogLevel.Warning, "Tried to set AIn{0} to 0x{1:X}, which is bigger than 10-bits; value has been truncated to 0x{2:X03}", idx, value, value & ADCDataMask);
83                 value &= ADCDataMask;
84             }
85             return value;
86         }
87 
UpdateInterrupts()88         private void UpdateInterrupts()
89         {
90             var pending = false;
91 
92             pending |= interruptDoneEnabled.Value && interruptDonePending.Value;
93             pending |= interruptReferenceReadyEnabled.Value && interruptReferenceReadyPending.Value;
94 
95             interruptAnyPending.Value = pending;
96             IRQ.Set(pending);
97         }
98 
DefineRegisters()99         private void DefineRegisters()
100         {
101             Registers.Control.Define(this)
102                 .WithFlag(0, FieldMode.WriteOneToClear, name: "CTRL.start",
103                     writeCallback: (_, value) =>
104                     {
105                         if(value)
106                         {
107                             interruptDonePending.Value = true;
108                             UpdateInterrupts();
109                         }
110                     })
111                 .WithTaggedFlag("CTRL.pwr", 1)
112                 .WithReservedBits(2, 1)
113                 .WithFlag(3, name: "CTRL.refbuf_pwr",
114                     writeCallback: (_, value) =>
115                     {
116                         if(value)
117                         {
118                             this.machine.LocalTimeSource.ExecuteInNearestSyncedState(__ =>
119                             {
120                                 interruptReferenceReadyPending.Value = true;
121                                 UpdateInterrupts();
122                             });
123                         }
124                     })
125                 .WithFlag(4, name: "CTRL.ref_sel")
126                 .WithReservedBits(5, 3)
127                 .WithTaggedFlag("CTRL.ref_scale", 8)
128                 .WithTaggedFlag("CTRL.input_scale", 9)
129                 .WithReservedBits(10, 1)
130                 .WithTaggedFlag("CTRL.clk_en", 11)
131                 .WithEnumField<DoubleWordRegister, Channels>(12, 4, out currentChannel, name: "CTRL.ch_sel")
132                 .WithReservedBits(16, 1)
133                 .WithEnumField<DoubleWordRegister, Alignment>(17, 1, out dataAlignment, name: "CTRL.data_align")
134                 .WithReservedBits(19, 13)
135             ;
136 
137             Registers.Status.Define(this)
138                 .WithFlag(0, FieldMode.Read, name: "STATUS.active",
139                     valueProviderCallback: _ => false)
140                 .WithReservedBits(1, 1)
141                 .WithTaggedFlag("STATUS.pwr_up_active", 2)
142                 .WithTaggedFlag("STATUS.overflow", 3)
143                 .WithReservedBits(4, 28)
144             ;
145 
146             Registers.OutputData.Define(this)
147                 .WithValueField(0, 16, name: "DATA.data",
148                     valueProviderCallback: _ =>
149                     {
150                         var data = GetValueFromActiveChannel();
151                         // Data can be justified either to LSB or MSB of the register.
152                         // When alignment is set to LSB, we don't have to do anything;
153                         // when it's set to MSB, we have to shift it in such way that
154                         // MSB of 10-bit data is at 15th position of the register.
155                         if(dataAlignment.Value == Alignment.MSB)
156                         {
157                             data <<= 6;
158                         }
159                         return data;
160                     })
161                 .WithReservedBits(16, 16)
162             ;
163 
164             Registers.InterruptControl.Define(this)
165                 .WithFlag(0, out interruptDoneEnabled, name: "INTR.done_ie")
166                 .WithFlag(1, out interruptReferenceReadyEnabled, name: "INTR.ref_ready_ie")
167                 .WithTaggedFlag("INTR.hi_limit_ie", 2)
168                 .WithTaggedFlag("INTR.lo_limit_ie", 3)
169                 .WithTaggedFlag("INTR.overflow_ie", 4)
170                 .WithReservedBits(5, 11)
171                 .WithFlag(16, out interruptDonePending, FieldMode.WriteOneToClear | FieldMode.Read, name: "INTR.done_if")
172                 .WithFlag(17, out interruptReferenceReadyPending, FieldMode.WriteOneToClear | FieldMode.Read, name: "INTR.ref_ready_if")
173                 .WithTaggedFlag("INTR.hi_limit_if", 18)
174                 .WithTaggedFlag("INTR.lo_limit_if", 19)
175                 .WithTaggedFlag("INTR.overflow_if", 20)
176                 .WithReservedBits(21, 1)
177                 .WithFlag(22, out interruptAnyPending, FieldMode.Read, name: "INTR.pending")
178                 .WithReservedBits(23, 9)
179                 .WithChangeCallback((_, __) => UpdateInterrupts())
180             ;
181 
182             for(var i = 0; i < LimitRegistersCount; ++i)
183             {
184                 (Registers.Limit0 + (long)(i * 0x04)).Define(this)
185                     .WithTag($"LIMIT{i}.ch_lo_limit", 0, 10)
186                     .WithReservedBits(10, 2)
187                     .WithTag($"LIMIT{i}.ch_hi_limit", 12, 10)
188                     .WithReservedBits(22, 2)
189                     .WithTag($"LIMIT{i}.ch_sel", 24, 4)
190                     .WithTaggedFlag($"LIMIT{i}.ch_lo_limit_en", 28)
191                     .WithTaggedFlag($"LIMIT{i}.ch_hi_limit_en", 29)
192                     .WithReservedBits(30, 2)
193                 ;
194             }
195         }
196 
197         private const int LimitRegistersCount = 4;
198         private const uint ADCDataMask = 0x3FF;
199 
200         private IEnumRegisterField<Alignment> dataAlignment;
201         private IEnumRegisterField<Channels> currentChannel;
202 
203         private IFlagRegisterField interruptDoneEnabled;
204         private IFlagRegisterField interruptReferenceReadyEnabled;
205 
206         private IFlagRegisterField interruptDonePending;
207         private IFlagRegisterField interruptReferenceReadyPending;
208         private IFlagRegisterField interruptAnyPending;
209 
210         private uint aIn0;
211         private uint aIn1;
212         private uint aIn2;
213         private uint aIn3;
214 
215         private enum Alignment
216         {
217             LSB = 0,
218             MSB,
219         }
220 
221         private enum Channels
222         {
223             AIn0 = 0,
224             AIn1,
225             AIn2,
226             AIn3,
227             AIn0Div5,
228             AIn1Div5,
229             VDDBDiv5,
230             VDDA,
231             VCORE,
232             VRTCDiv2,
233             Reserved,
234             VDDIODiv4,
235             VDDIOHDiv4,
236         }
237 
238         private enum Registers : long
239         {
240             Control = 0x00,
241             Status = 0x04,
242             OutputData = 0x08,
243             InterruptControl = 0x0C,
244             Limit0 = 0x10,
245             Limit1 = 0x14,
246             Limit2 = 0x18,
247             Limit3 = 0x1C,
248         }
249     }
250 }
251