// // Copyright (c) 2010-2021 Antmicro // // This file is licensed under the MIT License. // Full license text is available in 'licenses/MIT.txt'. // using System.Collections.Generic; using Antmicro.Renode.Core; using Antmicro.Renode.Utilities; using Antmicro.Renode.Peripherals.Bus; using Antmicro.Renode.Core.Structure.Registers; using Antmicro.Renode.Logging; namespace Antmicro.Renode.Peripherals.GPIOPort { public class ESP32S3_GPIO : BaseGPIOPort, IDoubleWordPeripheral, IKnownSize { public ESP32S3_GPIO(Machine machine) : base(machine, NumberOfPins) { locker = new object(); IRQ = new GPIO(); registers = new DoubleWordRegisterCollection(this, BuildRegisterMap()); data = new bool[NumberOfPins]; directionOutNotIn = new bool[NumberOfPins]; interruptEnabled = new bool[NumberOfPins]; interruptRequest = new bool[NumberOfPins]; edgeSelect = new bool[NumberOfPins]; interruptConfig = new InterruptConfig[NumberOfPins]; } public override void Reset() { lock(locker) { base.Reset(); IRQ.Unset(); registers.Reset(); for(var i = 0; i < NumberOfPins; ++i) { data[i] = false; directionOutNotIn[i] = false; interruptEnabled[i] = false; interruptRequest[i] = false; edgeSelect[i] = false; interruptConfig[i] = InterruptConfig.Low; } } } public uint ReadDoubleWord(long offset) { lock(locker) { return registers.Read(offset); } } public void WriteDoubleWord(long offset, uint value) { lock(locker) { registers.Write(offset, value); } } public override void OnGPIO(int number, bool value) { // if(!CheckPinNumber(number)) // { // return; // } // if(directionOutNotIn[number]) // { // this.Log(LogLevel.Warning, "gpio {0} is set to output, signal ignored.", number); // return; // } // lock(locker) // { // var previousState = State[number]; // base.OnGPIO(number, value); // UpdateSingleInterruptRequest(number, value, previousState != value); // UpdateIRQ(); // } } public long Size => 0x634; public GPIO IRQ { get; } private Dictionary BuildRegisterMap() { var registersDictionary = new Dictionary { {(long)Registers.STRAP, new DoubleWordRegister(this) .WithFlag(2, FieldMode.Read, valueProviderCallback: (_) => !EnableBootMessages, name: "GPIO46") // GND = enable boot messages .WithFlag(3, FieldMode.Read, valueProviderCallback: (_) => !EnterBootloader, name: "GPIO0") // GND = enter bootloader .WithFlag(4, FieldMode.Read, valueProviderCallback: (_) => false, name: "GPIO45") // GND = VDD_SPI @ 3.3V .WithFlag(5, FieldMode.Read, valueProviderCallback: (_) => false, name: "GPIO3") // GND = JTAG from pins, not USB }, // {(long)Registers.Data, new DoubleWordRegister(this) // .WithFlags(0, NumberOfPins, name: "GR / GPIO data register", // writeCallback: (id, _, val) => { data[id] = val; }, // valueProviderCallback: (id, _) => // { // return (directionOutNotIn[id]) // ? data[id] // : State[id]; // }) // .WithWriteCallback((_, __) => UpdateConnections()) // }, // {(long)Registers.Direction, new DoubleWordRegister(this) // .WithFlags(0, NumberOfPins, name: "GDIR / GPIO direction register", // writeCallback: (id, _, val) => { directionOutNotIn[id] = val; }, // valueProviderCallback: (id, _) => directionOutNotIn[id]) // .WithWriteCallback((_, __) => UpdateConnections()) // }, // {(long)Registers.PadStatus, new DoubleWordRegister(this) // .WithFlags(0, NumberOfPins, FieldMode.Read, name: "PSR / GPIO pad status register", // valueProviderCallback: (id, _) => State[id]) // }, // {(long)Registers.Mask, new DoubleWordRegister(this) // .WithFlags(0, NumberOfPins, name: "IMR / GPIO interrupt mask register", // writeCallback: (id, _, val) => { interruptEnabled[id] = val; }, // valueProviderCallback: (id, _) => interruptEnabled[id]) // .WithWriteCallback((_, __) => UpdateIRQ()) // }, // {(long)Registers.Status, new DoubleWordRegister(this) // .WithFlags(0, NumberOfPins, FieldMode.Read | FieldMode.WriteOneToClear, name: "ISR / GPIO interrupt status register", // writeCallback: (id, _, val) => // { // if(val) // { // interruptRequest[id] = false; // } // }, // valueProviderCallback: (id, _) => interruptRequest[id]) // .WithWriteCallback((_, __) => UpdateIRQ()) // }, // {(long)Registers.EdgeSelect, new DoubleWordRegister(this) // .WithFlags(0, NumberOfPins, name: "EDGE_SEL / GPIO edge select register", // writeCallback: (id, _, val) => { edgeSelect[id] = val; }, // valueProviderCallback: (id, _) => edgeSelect[id]) // }, // {(long)Registers.DataSet, new DoubleWordRegister(this) // .WithFlags(0, NumberOfPins, FieldMode.Write, name: "DR_SET / GPIO data register SET", // writeCallback: (id, _, __) => { data[id] = true; }) // .WithWriteCallback((_, __) => UpdateConnections()) // }, // {(long)Registers.DataClear, new DoubleWordRegister(this) // .WithFlags(0, NumberOfPins, FieldMode.Write, name: "DR_CLEAR / GPIO data register CLEAR", // writeCallback: (id, _, __) => { data[id] = false; }) // .WithWriteCallback((_, __) => UpdateConnections()) // }, // {(long)Registers.DataToggle, new DoubleWordRegister(this) // .WithFlags(0, NumberOfPins, FieldMode.Write, name: "DR_TOGGLE / GPIO data register TOGGLE", // writeCallback: (id, _, __) => { data[id] ^= true; }) // .WithWriteCallback((_, __) => UpdateConnections()) // }, }; // var config1 = new DoubleWordRegister(this); // var config2 = new DoubleWordRegister(this); // var half = NumberOfPins / 2; // for(var i = 0; i < half; ++i) // { // var j = i; // config1.WithEnumField(j * 2, 2, // name: $"ICR{j} / Interrupt configuration {j}", // writeCallback: (_, val) => { interruptConfig[j] = val; }, // valueProviderCallback: _ => interruptConfig[j]); // config2.WithEnumField(j * 2, 2, // name: $"ICR{half + j} / Interrupt configuration {half + j}", // writeCallback: (_, val) => { interruptConfig[half + j] = val; }, // valueProviderCallback: _ => interruptConfig[half + j]); // } // config1.WithWriteCallback((_, __) => UpdateAllInterruptRequests()); // config2.WithWriteCallback((_, __) => UpdateAllInterruptRequests()); // registersDictionary.Add((long)Registers.Config1, config1); // registersDictionary.Add((long)Registers.Config2, config2); return registersDictionary; } private void UpdateIRQ() { var flag = false; for(var i = 0; i < NumberOfPins; ++i) { flag |= interruptEnabled[i] && interruptRequest[i]; } IRQ.Set(flag); } private void UpdateConnections() { for(var i = 0; i < NumberOfPins; ++i) { Connections[i].Set(directionOutNotIn[i] && data[i]); } UpdateIRQ(); } private void UpdateAllInterruptRequests() { for(var i = 0; i < NumberOfPins; ++i) { UpdateSingleInterruptRequest(i, State[i]); } UpdateIRQ(); } private void UpdateSingleInterruptRequest(int i, bool currentState, bool stateChanged = false) { if(edgeSelect[i]) { interruptRequest[i] |= stateChanged; } else { switch(interruptConfig[i]) { case InterruptConfig.Low: interruptRequest[i] |= !currentState; break; case InterruptConfig.High: interruptRequest[i] |= currentState; break; case InterruptConfig.Rising: interruptRequest[i] |= stateChanged && currentState; break; case InterruptConfig.Falling: interruptRequest[i] |= stateChanged && !currentState; break; default: this.Log(LogLevel.Error, "Invalid state (interruptConfig[{0}]: 0x{1:X}).", i, interruptConfig[i]); break; } } } private readonly DoubleWordRegisterCollection registers; private readonly object locker; private readonly bool[] data; private readonly bool[] directionOutNotIn; private readonly bool[] interruptEnabled; private readonly bool[] interruptRequest; private readonly bool[] edgeSelect; private readonly InterruptConfig[] interruptConfig; public bool EnableBootMessages = true; public bool EnterBootloader = false; private const int NumberOfPins = 54; private enum InterruptConfig { Low = 0b00, High = 0b01, Rising = 0b10, Falling = 0b11, } private enum Registers : long { STRAP = 0x38, } } }