1 //
2 // Copyright (c) 2010-2024 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System.Collections.Generic;
8 using System.Collections.ObjectModel;
9 
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Core.Structure.Registers;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Utilities;
14 
15 namespace Antmicro.Renode.Peripherals.IRQControllers
16 {
17     public class STM32WBA_EXTI : BasicDoubleWordPeripheral, IKnownSize, ILocalGPIOReceiver, INumberedGPIOOutput
18     {
STM32WBA_EXTI(IMachine machine, int numberOfOutputLines)19         public STM32WBA_EXTI(IMachine machine, int numberOfOutputLines): base(machine)
20         {
21             this.numberOfLines = numberOfOutputLines;
22             core = new STM32_EXTICore(this, lineConfigurableMask: 0x1FFFF, separateConfigs: true);
23             var innerConnections = new Dictionary<int, IGPIO>();
24             for(var i = 0; i < numberOfOutputLines; ++i)
25             {
26                 innerConnections[i] = new GPIO();
27             }
28             Connections = new ReadOnlyDictionary<int, IGPIO>(innerConnections);
29             internalReceiversCache = new Dictionary<int, InternalReceiver>();
30             DefineRegisters();
31         }
32 
GetLocalReceiver(int index)33         public IGPIOReceiver GetLocalReceiver(int index)
34         {
35             if(!internalReceiversCache.TryGetValue(index, out var receiver))
36             {
37                 receiver = new InternalReceiver(this, index);
38                 internalReceiversCache.Add(index, receiver);
39             }
40             return receiver;
41         }
42 
Reset()43         public override void Reset()
44         {
45             base.Reset();
46             foreach(var connection in Connections.Values)
47             {
48                 connection.Unset();
49             }
50             foreach(var receiver in internalReceiversCache.Values)
51             {
52                 for(int pin = 0; pin < GpioPins; ++pin)
53                 {
54                     receiver.UpdateGPIO(pin);
55                 }
56             }
57         }
58 
59         public long Size => 0x1000;
60 
61         public IReadOnlyDictionary<int, IGPIO> Connections { get; }
62 
DefineRegisters()63         private void DefineRegisters()
64         {
65             RegistersCollection.DefineRegister((long)Registers.RaisingTriggerSelection)
66                 .WithValueField(0, 17, out core.RisingEdgeMask, name: "RT")
67                 .WithReservedBits(17, 15);
68 
69             RegistersCollection.DefineRegister((long)Registers.FallingTriggerSelection)
70                 .WithValueField(0, 17, out core.FallingEdgeMask, name: "FT")
71                 .WithReservedBits(17, 15);
72 
73             RegistersCollection.DefineRegister((long)Registers.SoftwareInterruptEvent)
74                 .WithValueField(0, 32, name: "SWIER", changeCallback: (_, value) =>
75                 {
76                     BitHelper.ForeachActiveBit(value & core.InterruptMask.Value, bit =>
77                     {
78                         Connections[bit].Set();
79                     });
80                 });
81 
82             RegistersCollection.DefineRegister((long)Registers.RaisingTriggerPending)
83                 .WithValueField(0, numberOfLines, out core.PendingRaisingInterrupts,
84                     writeCallback: (_, val) => BitHelper.ForeachActiveBit(val, x => Connections[x].Unset()), name: "RPIF");
85 
86             RegistersCollection.DefineRegister((long)Registers.FallingTriggerPending)
87                 .WithValueField(0, numberOfLines, out core.PendingFallingInterrupts,
88                     writeCallback: (_, val) => BitHelper.ForeachActiveBit(val, x => Connections[x].Unset()), name: "FPIF");
89 
90             RegistersCollection.DefineRegister((long)Registers.SecurityConfiguration);
91 
92             RegistersCollection.DefineRegister((long)Registers.PrivilegeConfiguration);
93 
94             for(var registerIndex = 0; registerIndex < InterruptSelectionRegistersCount; registerIndex++)
95             {
96                 var reg = new DoubleWordRegister(this, 0);
97                 for(var fieldNumber = 0; fieldNumber < NumberOfPortsPerInterruptSelectionRegister; ++fieldNumber)
98                 {
99                     var pinNumber = registerIndex * 4 + fieldNumber;
100                     extiMappings[pinNumber] = reg.DefineValueField(8 * fieldNumber, 8, name: $"EXTI{pinNumber}",
101                         changeCallback: (_, portNumber) =>
102                         {
103                             Connections[pinNumber].Unset();
104                             ((InternalReceiver)GetLocalReceiver((int)portNumber)).UpdateGPIO(pinNumber);
105                         }
106                     );
107                 }
108                 RegistersCollection.AddRegister((long)Registers.ExternalInterruptSelection1 + 4 * registerIndex, reg);
109             }
110 
111             RegistersCollection.DefineRegister((long)Registers.Lock);
112 
113             RegistersCollection.DefineRegister((long)Registers.WakeUpInterruptMask);
114 
115             RegistersCollection.DefineRegister((long)Registers.WakeUpEventMask);
116         }
117 
118         private readonly STM32_EXTICore core;
119         private readonly int numberOfLines;
120         private readonly Dictionary<int, InternalReceiver> internalReceiversCache;
121         private readonly IValueRegisterField[] extiMappings = new IValueRegisterField[GpioPins];
122 
123         private const uint InterruptSelectionRegistersCount = 4;
124         private const int GpioPins = 16;
125         private const int NumberOfPortsPerInterruptSelectionRegister = GpioPins / (int)InterruptSelectionRegistersCount;
126 
127         private class InternalReceiver : IGPIOReceiver
128         {
InternalReceiver(STM32WBA_EXTI parent, int portNumber)129             public InternalReceiver(STM32WBA_EXTI parent, int portNumber)
130             {
131                 this.parent = parent;
132                 this.portNumber = portNumber;
133                 this.state = new bool[GpioPins];
134             }
135 
OnGPIO(int pinNumber, bool value)136             public void OnGPIO(int pinNumber, bool value)
137             {
138                 if(pinNumber >= GpioPins)
139                 {
140                     parent.Log(LogLevel.Error, "GPIO port {0}, pin {1}, is not supported. Up to {2} pins are supported", portNumber, pinNumber, GpioPins);
141                     return;
142                 }
143                 parent.Log(LogLevel.Noisy, "GPIO port {0}, pin {1}, raised IRQ: {2}", portNumber, pinNumber, value);
144                 state[pinNumber] = value;
145 
146                 UpdateGPIO(pinNumber);
147             }
148 
UpdateGPIO(int pinNumber)149             public void UpdateGPIO(int pinNumber)
150             {
151                 if((int)parent.extiMappings[pinNumber].Value == portNumber)
152                 {
153                     var value = state[pinNumber];
154                     if(parent.core.CanSetInterruptValue((byte)pinNumber, value, out var _))
155                     {
156                         parent.core.UpdatePendingValue((byte)pinNumber, true);
157                         parent.Connections[pinNumber].Set(true);
158                     }
159                 }
160             }
161 
Reset()162             public void Reset()
163             {
164                 // IRQs are cleared on Parent reset
165                 // Don't clear `state` array here - as it represents the state of input signals, and is not a property of this peripheral
166                 // The state can only be cleared when the input signal is reset - but it's not controlled by us, but by the peripheral connected to OnGPIO (IRQ/GPIO line)
167                 // and since peripherals with connected GPIOs will naturally unset them in their Reset, the state array won't contain stale data
168             }
169 
170             // The state is recorded, so it's possible to update the GPIO state when changing source peripheral
171             // since this peripheral is effectively a mux - when changing input source, it's needed to get the other source's value
172             private readonly bool[] state;
173             private readonly STM32WBA_EXTI parent;
174             private readonly int portNumber;
175         }
176 
177 
178         private enum Registers
179         {
180             RaisingTriggerSelection     = 0x0,  // EXTI_RTSR1
181             FallingTriggerSelection     = 0x4,  // EXTI_FTSR1
182             SoftwareInterruptEvent      = 0x8,  // EXTI_SWIER1
183             RaisingTriggerPending       = 0xc,  // EXTI_RPR1
184             FallingTriggerPending       = 0x10, // EXTI_FPR1
185             SecurityConfiguration       = 0x14, // EXTI_SECCFGR1
186             PrivilegeConfiguration      = 0x18, // EXTI_PRIVCFGR1
187             ExternalInterruptSelection1 = 0x60, // EXTI_EXTICR1
188             ExternalInterruptSelection2 = 0x64, // EXTI_EXTICR2
189             ExternalInterruptSelection3 = 0x68, // EXTI_EXTICR3
190             ExternalInterruptSelection4 = 0x6c, // EXTI_EXTICR4
191             Lock                        = 0x70, // EXTI_LOCKR
192             WakeUpInterruptMask         = 0x80, // EXTI_IMR1
193             WakeUpEventMask             = 0x84, // EXTI_EMR1
194         }
195     }
196 }
197