Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions src/Emulator/Peripherals/Peripherals/GPIOPort/Aspeed_GPIO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//
// Copyright (c) 2026 Microsoft
// Licensed under the MIT License.
//

using System;
using System.Collections.Generic;
using Antmicro.Renode.Core;
using Antmicro.Renode.Core.Structure.Registers;
using Antmicro.Renode.Logging;
using Antmicro.Renode.Peripherals.Bus;

namespace Antmicro.Renode.Peripherals.GPIOPort
{
// Aspeed AST2600 GPIO Controller
// Reference: QEMU hw/gpio/aspeed_gpio.c
//
// 3.3V controller (0x1E780000): 7 sets (ABCD..YZAAAB), 208 pins, IRQ 40
// 1.8V controller (0x1E780800): 2 sets (18ABCD, 18E), 36 pins, IRQ 11
//
// R/W register file. INT_STATUS registers are W1C (write-1-to-clear).
// DATA_READ registers return corresponding DATA_VALUE.
[AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)]
public sealed class Aspeed_GPIO : IDoubleWordPeripheral, IKnownSize, IGPIOSender
{
public Aspeed_GPIO(int numberOfSets = 7)
{
this.numberOfSets = numberOfSets;
storage = new uint[RegisterSpaceSize / 4];

// Build lookup tables for special registers
intStatusOffsets = new HashSet<uint>();
dataReadMap = new Dictionary<uint, uint>();

if(numberOfSets >= 7)
{
// 3.3V GPIO: 7 sets
BuildSetMappings_3_3V();
}
else
{
// 1.8V GPIO: 2 sets
BuildSetMappings_1_8V();
}

Reset();
}

public long Size => RegisterSpaceSize;

public GPIO IRQ { get; } = new GPIO();

public void Reset()
{
Array.Clear(storage, 0, storage.Length);
}

public uint ReadDoubleWord(long offset)
{
if(offset < 0 || offset >= RegisterSpaceSize)
{
return 0;
}

var byteOff = (uint)offset;

// DATA_READ registers return the corresponding DATA_VALUE
if(dataReadMap.TryGetValue(byteOff, out var dataValueOff))
{
return storage[dataValueOff / 4];
}

return storage[byteOff / 4];
}

public void WriteDoubleWord(long offset, uint value)
{
if(offset < 0 || offset >= RegisterSpaceSize)
{
return;
}

var byteOff = (uint)offset;
var reg = byteOff / 4;

// DATA_READ registers are read-only
if(dataReadMap.ContainsKey(byteOff))
{
return;
}

// INT_STATUS registers are W1C
if(intStatusOffsets.Contains(byteOff))
{
storage[reg] &= ~value;
UpdateIRQ();
return;
}

storage[reg] = value;
}

private void UpdateIRQ()
{
bool anyPending = false;
foreach(var off in intStatusOffsets)
{
var enableOff = off - 0x10; // INT_ENABLE is 0x10 before INT_STATUS in each set's block
if(storage[off / 4] != 0)
{
anyPending = true;
break;
}
}
IRQ.Set(anyPending);
}

private void BuildSetMappings_3_3V()
{
// INT_STATUS offsets (byte addresses) — W1C
uint[] intStatOffsets = { 0x018, 0x038, 0x0A8, 0x0F8, 0x128, 0x158, 0x188 };
foreach(var off in intStatOffsets)
{
intStatusOffsets.Add(off);
}

// DATA_READ → DATA_VALUE mappings
dataReadMap[0x0C0] = 0x000; // ABCD
dataReadMap[0x0C4] = 0x020; // EFGH
dataReadMap[0x0C8] = 0x070; // IJKL
dataReadMap[0x0CC] = 0x078; // MNOP
dataReadMap[0x0D0] = 0x080; // QRST
dataReadMap[0x0D4] = 0x088; // UVWX
dataReadMap[0x0D8] = 0x1E0; // YZAAAB
}

private void BuildSetMappings_1_8V()
{
// 1.8V has same layout as first 2 sets of 3.3V
intStatusOffsets.Add(0x018); // 18ABCD
intStatusOffsets.Add(0x038); // 18E

dataReadMap[0x0C0] = 0x000; // 18ABCD
dataReadMap[0x0C4] = 0x020; // 18E
}

private readonly int numberOfSets;
private readonly uint[] storage;
private readonly HashSet<uint> intStatusOffsets;
private readonly Dictionary<uint, uint> dataReadMap;

private const int RegisterSpaceSize = 0x800;
}
}
204 changes: 204 additions & 0 deletions src/Emulator/Peripherals/Peripherals/I2C/Aspeed_I2C.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//
// Copyright (c) 2026 Microsoft
// Licensed under the MIT License.
//

using System;
using System.Collections.Generic;
using Antmicro.Renode.Core;
using Antmicro.Renode.Logging;
using Antmicro.Renode.Peripherals.Bus;

namespace Antmicro.Renode.Peripherals.I2C
{
// Aspeed AST2600 I2C Controller — 16 buses
// Reference: QEMU hw/i2c/aspeed_i2c.c (AST2600 "new mode")
//
// Memory map (0x1000 total):
// 0x000-0x00F: Global control
// 0x080+N*0x80: Bus N registers (N=0..15)
// 0xC00-0xDFF: Shared buffer pool
//
// Stub behavior: empty buses return TX_NAK on START+TX commands.
// Linux aspeed-i2c driver probes successfully with NACKs.
[AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)]
public sealed class Aspeed_I2C : IDoubleWordPeripheral, IKnownSize, INumberedGPIOOutput
{
public Aspeed_I2C()
{
storage = new uint[RegisterSpaceSize / 4];
var gpios = new Dictionary<int, IGPIO>();
for(int i = 0; i < NumBuses; i++)
{
gpios[i] = new GPIO();
}
Connections = gpios;
Reset();
}

public long Size => RegisterSpaceSize;

public IReadOnlyDictionary<int, IGPIO> Connections { get; }

public void Reset()
{
Array.Clear(storage, 0, storage.Length);
}

public uint ReadDoubleWord(long offset)
{
if(offset < 0 || offset >= RegisterSpaceSize)
{
return 0;
}
return storage[(uint)offset / 4];
}

public void WriteDoubleWord(long offset, uint value)
{
if(offset < 0 || offset >= RegisterSpaceSize)
{
return;
}

var reg = (uint)offset / 4;

// Determine if this is a bus register
int busIndex = GetBusIndex((uint)offset);
if(busIndex >= 0)
{
uint busLocalOffset = ((uint)offset - BusBase) % BusSpacing;
HandleBusWrite(busIndex, busLocalOffset, value);
return;
}

// Global registers and pool: plain R/W
storage[reg] = value;
}

private void HandleBusWrite(int bus, uint localOffset, uint value)
{
uint absOffset = BusBase + (uint)bus * BusSpacing + localOffset;
uint reg = absOffset / 4;

switch(localOffset)
{
case MasterIntrSts: // I2CM_INTR_STS — W1C
storage[reg] &= ~(value & 0xF007F07F);
UpdateBusIRQ(bus);
return;

case SlaveIntrSts: // I2CS_INTR_STS — W1C
storage[reg] &= ~value;
UpdateBusIRQ(bus);
return;

case MasterCmd: // I2CM_CMD — command execution
storage[reg] = value;
HandleMasterCommand(bus, value);
return;

case AcTiming:
storage[reg] = value & 0x1FFFF0FF;
return;

case MasterIntrCtrl:
storage[reg] = value & 0x0007F07F;
return;

default:
storage[reg] = value;
return;
}
}

private void HandleMasterCommand(int bus, uint cmd)
{
uint absOffset = BusBase + (uint)bus * BusSpacing;
uint intrStsReg = (absOffset + MasterIntrSts) / 4;

bool startCmd = (cmd & CmdStartBit) != 0;
bool txCmd = (cmd & CmdTxBit) != 0;
bool rxCmd = (cmd & CmdRxBit) != 0;
bool stopCmd = (cmd & CmdStopBit) != 0;

if(startCmd && txCmd)
{
// START + TX: send address byte → NACK (no devices on stub bus)
storage[intrStsReg] |= IntrTxNak;
}
else if(txCmd)
{
// Data TX → NACK
storage[intrStsReg] |= IntrTxNak;
}
else if(rxCmd)
{
// RX → NACK (no device to receive from)
storage[intrStsReg] |= IntrTxNak;
}

if(stopCmd)
{
storage[intrStsReg] |= IntrNormalStop;
}

// Clear the command bits (command consumed)
storage[(absOffset + MasterCmd) / 4] &= ~(CmdStartBit | CmdTxBit | CmdRxBit | CmdStopBit);

UpdateBusIRQ(bus);
}

private void UpdateBusIRQ(int bus)
{
uint absOffset = BusBase + (uint)bus * BusSpacing;
uint intrSts = storage[(absOffset + MasterIntrSts) / 4];
uint intrCtrl = storage[(absOffset + MasterIntrCtrl) / 4];

bool pending = (intrSts & intrCtrl) != 0;
((GPIO)Connections[bus]).Set(pending);
}

private int GetBusIndex(uint offset)
{
if(offset < BusBase || offset >= BusBase + NumBuses * BusSpacing)
{
return -1;
}
return (int)((offset - BusBase) / BusSpacing);
}

private readonly uint[] storage;

// Layout
private const uint BusBase = 0x80;
private const uint BusSpacing = 0x80;
private const int NumBuses = 16;

// Per-bus register offsets (relative to bus base)
private const uint FunCtrl = 0x00;
private const uint AcTiming = 0x04;
private const uint TxRxByteBuf = 0x08;
private const uint PoolCtrl = 0x0C;
private const uint MasterIntrCtrl = 0x10;
private const uint MasterIntrSts = 0x14;
private const uint MasterCmd = 0x18;
private const uint SlaveIntrCtrl = 0x20;
private const uint SlaveIntrSts = 0x24;

// Command bits
private const uint CmdStartBit = 1u << 0;
private const uint CmdTxBit = 1u << 1;
private const uint CmdRxBit = 1u << 3;
private const uint CmdStopBit = 1u << 5;

// Interrupt status bits
private const uint IntrTxAck = 1u << 0;
private const uint IntrTxNak = 1u << 1;
private const uint IntrRxDone = 1u << 2;
private const uint IntrNormalStop = 1u << 4;
private const uint IntrAbnormal = 1u << 5;

private const int RegisterSpaceSize = 0x1000;
}
}
Loading