| // |
| // 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; |
| } |
| } |