blob: 037ab3543070292937a28ef49b11abdf1b542d17 [file] [log] [blame]
//
// Copyright (c) 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Antmicro.Renode.Core;
using Antmicro.Renode.Core.Structure.Registers;
using Antmicro.Renode.Exceptions;
using Antmicro.Renode.Logging;
using Antmicro.Renode.Peripherals.Miscellaneous;
using Antmicro.Renode.Peripherals.Bus;
using Antmicro.Renode.Utilities;
using Antmicro.Renode.Debugging;
using Antmicro.Renode.Sound;
namespace Antmicro.Renode.Peripherals.Sound
{
public class Fifo
{
public void Write(ulong value)
{
lock (synclock)
{
if (!Full())
{
fifo[cursor_w] = value;
cursor_w = (cursor_w + 1) & FIFO_MASK;
count++;
}
}
}
public ulong Read()
{
ulong result = 0;
lock (synclock)
{
if (!Empty())
{
result = fifo[cursor_r];
cursor_r = (cursor_r + 1) & FIFO_MASK;
count--;
}
return result;
}
}
public bool Empty()
{
return count == 0;
}
public bool Full()
{
return count == FIFO_SIZE;
}
public void Flush()
{
lock (synclock)
{
cursor_r = 0;
cursor_w = 0;
count = 0;
for (int i = 0; i < FIFO_SIZE; i++)
fifo[i] = 0;
}
}
const uint FIFO_SIZE = 64 * 2;
const uint FIFO_MASK = FIFO_SIZE - 1;
public uint cursor_r = 0;
public uint cursor_w = 0;
public uint count = 0;
public ulong[] fifo = new ulong[FIFO_SIZE];
private readonly object synclock = new object();
}
public class MatchaI2S : BasicDoubleWordPeripheral, IDisposable, IKnownSize, INumberedGPIOOutput
{
public MatchaI2S(Machine machine) : base(machine)
{
CreateRegisters();
TxWatermarkIRQ = new GPIO();
RxWatermarkIRQ = new GPIO();
TxEmptyIRQ = new GPIO();
RxOverflowIRQ = new GPIO();
var irqs = new Dictionary<int, IGPIO>();
irqs[(int)Events.TxWatermarkIRQ] = TxWatermarkIRQ;
irqs[(int)Events.RxWatermarkIRQ] = RxWatermarkIRQ;
irqs[(int)Events.TxEmptyIRQ] = TxEmptyIRQ;
irqs[(int)Events.RxOverflowIRQ] = RxOverflowIRQ;
Connections = new ReadOnlyDictionary<int, IGPIO>(irqs);
Reset();
}
public override void Reset()
{
base.Reset();
InputFile = "";
OutputFile = "";
ResetTx();
ResetRx();
}
private void ResetTx()
{
encoder?.FlushBuffer();
txThread = null;
txBuffer = new Fifo();
}
private void ResetRx()
{
decoder?.Reset();
rxThread = null;
rxBuffer = new Fifo();
rxTriggerLevel.Value = 0b010;
mappedRxTriggerLevel = 8;
}
public void Dispose()
{
encoder?.Dispose();
}
public string InputFile { get; set; }
public string OutputFile { get; set; }
public const uint SampleFrequencyHz = 16_000;
public const uint SampleSizeBits = 16;
public const uint NumChannels = 2;
public const uint BufferSizeWords = 64;
private Fifo rxBuffer;
private Fifo txBuffer;
public IReadOnlyDictionary<int, IGPIO> Connections { get; }
private void StartRx()
{
if (InputFile == "") {
this.Log(LogLevel.Error, "Starting recording without an input file!");
return;
}
decoder = new PCMDecoder(SampleSizeBits, SampleFrequencyHz, NumChannels, false, this);
decoder.LoadFile(InputFile);
this.Log(LogLevel.Info, "Starting recording.");
rxThread = machine.ObtainManagedThread(InputFrames, SampleFrequencyHz);
rxThread.Start();
}
private void StartTx()
{
}
private void StopThread(ref IManagedThread thread)
{
thread?.Stop();
thread = null;
}
private void OutputFrames()
{
}
private void InputFrames()
{
while (!rxBuffer.Full())
{
// Decode left on the upper half of the sample
ulong sample = (decoder.GetSingleSample() << 16);
sample |= decoder.GetSingleSample();
if (!rxBuffer.Full()) {
rxBuffer.Write(sample);
if ((rxBuffer.count == mappedRxTriggerLevel) && (rxWatermarkIrqEnabled.Value))
{
this.Log(LogLevel.Debug, "I2S RX FIFO Watermark.");
Connections[(int)Events.RxWatermarkIRQ].Blink();
}
}
}
if (rxOverflowIrqEnabled.Value) {
Connections[(int)Events.RxOverflowIRQ].Blink();
}
}
private void CreateRegisters()
{
Registers.IrqState.Define(this)
.WithFlag(0, name: "TX_WATERMARK", valueProviderCallback: _ => { return txBuffer.count >= mappedTxTriggerLevel; })
.WithFlag(1, name: "RX_WATERMARK", valueProviderCallback: _ => { return rxBuffer.count >= mappedRxTriggerLevel; })
.WithFlag(2, name: "TX_EMPTY", valueProviderCallback: _ => { return txBuffer.Empty(); })
.WithFlag(3, name: "RX_OVERFLOW", valueProviderCallback: _ => { return rxBuffer.Full(); });
Registers.IrqEnable.Define(this)
.WithFlag(0, out txWatermarkIrqEnabled, name: "ENABLE_TX_WATERMARK")
.WithFlag(1, out rxWatermarkIrqEnabled, name: "ENABLE_RX_WATERMARK")
.WithFlag(2, out txEmptyIrqEnabled, name: "ENABLE_TX_EMPTY")
.WithFlag(3, out rxOverflowIrqEnabled, name: "ENABLE_RX_OVERFLOW");
Registers.IrqTest.Define(this)
.WithFlag(0, name: "TEST_TX_WATERMARK",
writeCallback: (_, __) => { Connections[(int)Events.TxWatermarkIRQ].Blink(); })
.WithFlag(1, name: "TEST_RX_WATERMARK",
writeCallback: (_, __) => { Connections[(int)Events.RxWatermarkIRQ].Blink(); })
.WithFlag(2, name: "TEST_TX_EMPTY",
writeCallback: (_, __) => { Connections[(int)Events.TxEmptyIRQ].Blink(); })
.WithFlag(3, name: "TEST_RX_OVERFLOW",
writeCallback: (_, __) => { Connections[(int)Events.RxOverflowIRQ].Blink(); });
Registers.Control.Define(this)
.WithFlag(0, out txEnable, name: "TX",
writeCallback: (_, val) => {
if (val) {
StartTx();
} else {
StopThread(ref txThread);
}
})
.WithFlag(1, out rxEnable, name: "RX",
writeCallback: (_, val) => {
if (val) {
StartRx();
} else {
StopThread(ref rxThread);
}
})
.WithFlag(2, out loopbackEnable, name: "SLPBK")
.WithReservedBits(3, 15)
.WithValueField(18, 6, out rxClkDivide, name: "NCO_RX")
.WithValueField(25, 6, out txClkDivide, name: "NCO_TX");
Registers.Status.Define32(this)
.WithFlag(0, name: "TXFULL", valueProviderCallback: _ => { return txBuffer.Full(); })
.WithFlag(1, name: "RXFULL", valueProviderCallback: _ => { return rxBuffer.Full(); })
.WithFlag(2, name: "TXEMPTY", valueProviderCallback: _ => { return txBuffer.Empty(); })
.WithFlag(3, name: "RXEMPTY", valueProviderCallback: _ => { return rxBuffer.Empty(); })
.WithReservedBits(4, 27);
Registers.ReadData.Define32(this)
.WithValueField(0, 31, FieldMode.Read, name: "RDATA",
valueProviderCallback: _ => { return rxBuffer.Read(); });
Registers.WriteData.Define32(this)
.WithValueField(0, 31, name: "WDATA",
writeCallback: (_, val) => { txBuffer.Write(val); });
Registers.FifoControl.Define32(this)
.WithFlag(0, name: "RXRST", writeCallback: (_, val) => { if (val) ResetRx(); })
.WithFlag(1, name: "TXRST", writeCallback: (_, val) => { if (val) ResetTx(); })
.WithValueField(2, 3, out rxTriggerLevel,
writeCallback: (_, val) => {
switch (val) {
case 0b000: mappedRxTriggerLevel = 1; break;
case 0b001: mappedRxTriggerLevel = 4; break;
case 0b010: mappedRxTriggerLevel = 8; break;
case 0b011: mappedRxTriggerLevel = 16; break;
case 0b100: mappedRxTriggerLevel = 30; break;
default:
this.Log(LogLevel.Error, "Trying to set invalid RX trigger level. Setting to default");
val = 0b010;
mappedRxTriggerLevel = 8;
break;
}
},
name: "RXILVL")
.WithValueField(5, 2, out txTriggerLevel,
writeCallback: (_, val) => {
switch (val) {
case 0b00: mappedTxTriggerLevel = 1; break;
case 0b01: mappedTxTriggerLevel = 4; break;
case 0b10: mappedTxTriggerLevel = 8; break;
case 0b11: mappedTxTriggerLevel = 16; break;
default:
this.Log(LogLevel.Error, "Trying to set invalid TX trigger level. Setting to default");
val = 0b00;
mappedTxTriggerLevel = 8;
break;
}
},
name: "TXILVL")
.WithReservedBits(7, 24);
Registers.FifoStatus.Define32(this)
.WithValueField(0, 6, name: "TXLVL",
valueProviderCallback: _ => { return txBuffer.count; })
.WithReservedBits(7, 9)
.WithValueField(16, 6, name: "RXLVL",
valueProviderCallback: _ => { return rxBuffer.count; })
.WithReservedBits(22, 10);
}
private IFlagRegisterField txEnable;
private IFlagRegisterField rxEnable;
private IFlagRegisterField loopbackEnable;
private IFlagRegisterField txWatermarkIrqEnabled;
private IFlagRegisterField rxWatermarkIrqEnabled;
private IFlagRegisterField txEmptyIrqEnabled;
private IFlagRegisterField rxOverflowIrqEnabled;
private IValueRegisterField rxClkDivide;
private IValueRegisterField txClkDivide;
private IValueRegisterField rxTriggerLevel;
private IValueRegisterField txTriggerLevel;
private int mappedRxTriggerLevel;
private int mappedTxTriggerLevel;
private IManagedThread rxThread;
private IManagedThread txThread;
private PCMDecoder decoder;
private PCMEncoder encoder;
public GPIO TxWatermarkIRQ { get; }
public GPIO RxWatermarkIRQ { get; }
public GPIO TxEmptyIRQ { get; }
public GPIO RxOverflowIRQ { get; }
private enum Events
{
TxWatermarkIRQ = 0, // raised if the xmit FIFO is below the high watermark
RxWatermarkIRQ = 1, // raised if the recv FIFO is above the high watermark
TxEmptyIRQ = 2, // raised if the xmit FIFO is empty
RxOverflowIRQ = 3, // raised if the recv FIFO has overflowed
}
private enum Registers
{
IrqState = 0x00, // Current interrupt states
// bit 0: TX watermark
// bit 1: RX watermark
// bit 2: TX empty
// bit 3: RX overflow
IrqEnable = 0x04, // Whether or not interrupts are enabled
// bit 0: TX watermark
// bit 1: RX watermark
// bit 2: TX empty
// bit 3: RX overflow
IrqTest = 0x08, // IRQ testing
// bit 0: TX watermark
// bit 1: RX watermark
// bit 2: TX empty
// bit 3: RX overflow
Control = 0x0c, // Control register
Status = 0x10, // I2S Live Status register
// bit 0: TX buffer full
// bit 1: RX buffer full
// bit 2: TX FIFO empty
// bit 3: RX FIFO empty
ReadData = 0x14, // I2S read data
WriteData = 0x18, // I2S write data
FifoControl = 0x1c, // I2S FIFO control register
// bit 0: RX FIFO reset (write 1 to reset)
// bit 1: TX FIFO reset (write 1 to reset)
// bit 4-2: Trigger level for RX interrupts
// bit 6-5: Trigger level for TX interrupts
FifoStatus = 0x20, // I2S FIFO status register
// bit 5:0: Current fill level of TX fifo
// bit 21:16: Current fill level of RX fifo.
}
public long Size => 0x40;
}
}