1 //
2 // Copyright (c) 2010-2023 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;
8 using System.Collections.ObjectModel;
9 using Antmicro.Renode.Peripherals.Bus;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Core.Structure.Registers;
12 using Antmicro.Renode.Logging;
13 using System.Collections.Generic;
14 using Antmicro.Renode.Utilities;
15 
16 namespace Antmicro.Renode.Peripherals.IRQControllers
17 {
18     public class STM32H7_EXTI : BasicDoubleWordPeripheral, IKnownSize, IIRQController, INumberedGPIOOutput
19     {
STM32H7_EXTI(IMachine machine)20         public STM32H7_EXTI(IMachine machine) : base(machine)
21         {
22             var innerConnections = new Dictionary<int, IGPIO>();
23             for(var i = 0; i < CoreCount * LinesPerCore; i++)
24             {
25                 innerConnections[i] = new GPIO();
26             }
27             Connections = new ReadOnlyDictionary<int, IGPIO>(innerConnections);
28 
29             cores = new STM32_EXTICore[CoreCount];
30             for(var i = 0; i < CoreCount; i++)
31             {
32                 cores[i] = new STM32_EXTICore(this, LineConfigurations[i]);
33             }
34 
35             DefineRegisters();
36             Reset();
37         }
38 
Reset()39         public override void Reset()
40         {
41             base.Reset();
42             foreach(var connection in Connections)
43             {
44                 connection.Value.Unset();
45             }
46         }
47 
OnGPIO(int number, bool value)48         public void OnGPIO(int number, bool value)
49         {
50             if(number >= NumberOfLines)
51             {
52                 this.Log(LogLevel.Error, "GPIO number {0} is out of range [0; {1})", number, NumberOfLines);
53                 return;
54             }
55 
56             var numberInCore = (byte)(number % LinesPerCore);
57             var core = cores[number / LinesPerCore];
58 
59             if(core.CanSetInterruptValue(numberInCore, value, out var isLineConfigurable))
60             {
61                 if(isLineConfigurable)
62                 {
63                     // Configurable line can only be set from this place
64                     value = true;
65                     core.UpdatePendingValue(numberInCore, true);
66                 }
67                 Connections[number].Set(value);
68             }
69         }
70 
71         public long Size => 0x400;
72 
73         public IReadOnlyDictionary<int, IGPIO> Connections { get; }
74         public long NumberOfLines => Connections.Count;
75 
DefineRegisters()76         private void DefineRegisters()
77         {
78             // We can't use DefineMany here as each of the InterruptMaskX registers has a different reset value
79             for(var idx = 0; idx < CoreCount; idx++)
80             {
81                 var core = cores[idx];
82 
83                 RegistersCollection.DefineRegister((long)Registers.RisingTriggerSelection1 + idx * 0x20)
84                     .WithValueField(0, 32, out core.RisingEdgeMask, name: "RTSR");
85 
86                 RegistersCollection.DefineRegister((long)Registers.FallingTriggerSelection1 + idx * 0x20)
87                     .WithValueField(0, 32, out core.FallingEdgeMask, name: "FTSR");
88 
89                 RegistersCollection.DefineRegister((long)Registers.SoftwareInterruptEvent1 + idx * 0x20)
90                     .WithValueField(0, 32, name: "SWIER", valueProviderCallback: _ => 0x0,
91                         changeCallback: (_, value) =>
92                         {
93                             BitHelper.ForeachActiveBit(value & core.InterruptMask.Value, bit =>
94                             {
95                                 var connectionNumber = LinesPerCore * idx + bit;
96                                 if(!Connections.TryGetValue(connectionNumber, out var irq))
97                                 {
98                                     this.Log(LogLevel.Warning, "Cannot set software interrupt on line {0} as it does not exist", connectionNumber);
99                                     return;
100                                 }
101                                 core.UpdatePendingValue(bit, true);
102                                 irq.Set();
103                             });
104                         });
105 
106                 // All configurable lines are by default masked. So the reset value is the inverse of if the line is configurable
107                 RegistersCollection.DefineRegister((long)Registers.InterruptMask1 + idx * 0x10, ~LineConfigurations[idx])
108                     .WithValueField(0, 32, out core.InterruptMask, name: "CPUIMR");
109 
110                 RegistersCollection.DefineRegister((long)Registers.Pending1 + idx * 0x10)
111                     .WithValueField(0, 32, out core.PendingInterrupts, FieldMode.Read | FieldMode.WriteOneToClear, name: "CPUPR",
112                         writeCallback: (_, value) => BitHelper.ForeachActiveBit(value, bit => Connections[bit].Unset()));
113             }
114         }
115 
116         private readonly STM32_EXTICore[] cores;
117 
118         // This is for the STM32H7 configuration
119         // Set bit means that this interrupt is configurable
120         private static readonly uint[] LineConfigurations = new uint[CoreCount]
121         {
122             0x3FFFFF,
123             0xA0000,
124             0x740000,
125         };
126 
127         private const int CoreCount = 3;
128         private const int LinesPerCore = 32;
129 
130         private enum Registers
131         {
132             RisingTriggerSelection1 = 0x00,         // EXTI_RTSR1
133             FallingTriggerSelection1 = 0x04,        // EXTI_FTSR1
134             SoftwareInterruptEvent1 = 0x08,         // EXTI_SWIER1
135             D3PendingMask1 = 0x0C,                  // EXTI_D3PMR1
136             D3PendingClearSelection1Low = 0x10,     // EXTI_D3PCR1L
137             D3PendingClearSelection1High = 0x14,    // EXTI_D3PCR1H
138             // Intentional gap
139             RisingTriggerSelection2 = 0x20,         // EXTI_RTSR2
140             FallingTriggerSelection2 = 0x24,        // EXTI_FTSR2
141             SoftwareInterruptEvent2 = 0x28,         // EXTI_SWIER2
142             D3PendingMask2 = 0x2C,                  // EXTI_D3PMR2
143             D3PendingClearSelection2Low = 0x30,     // EXTI_D3PCR2L
144             D3PendingClearSelection2High = 0x34,    // EXTI_D3PCR2H
145             // Intentional gap
146             RisingTriggerSelection3 = 0x40,         // EXTI_RTSR3
147             FallingTriggerSelection3 = 0x44,        // EXTI_FTSR3
148             SoftwareInterruptEvent3 = 0x48,         // EXTI_SWIER3
149             D3PendingMask3 = 0x4C,                  // EXTI_D3PMR3
150             D3PendingClearSelection3Low = 0x50,     // EXTI_D3PCR3L
151             D3PendingClearSelection3High = 0x54,    // EXTI_D3PCR3H
152             // Intentional gap
153             InterruptMask1 = 0x80,                  // EXTI_CPUIMR1
154             EventMask1 = 0x84,                      // EXTI_CPUEMR1
155             Pending1 = 0x88,                        // EXTI_CPUPR1
156             // Intentional gap
157             InterruptMask2 = 0x90,                  // EXTI_CPUIMR2
158             EventMask2 = 0x94,                      // EXTI_CPUEMR2
159             Pending2 = 0x98,                        // EXTI_CPUPR2
160             // Intentional gap
161             InterruptMask3 = 0xA0,                  // EXTI_CPUIMR3
162             EventMask3 = 0xA4,                      // EXTI_CPUEMR3
163             Pending3 = 0xA8,                        // EXTI_CPUPR3
164         }
165     }
166 }
167