194 lines
6.4 KiB
C#
194 lines
6.4 KiB
C#
|
//
|
|||
|
// Copyright (c) 2010-2018 Antmicro
|
|||
|
// Copyright (c) 2011-2015 Realtime Embedded
|
|||
|
//
|
|||
|
// This file is licensed under the MIT License.
|
|||
|
// Full license text is available in 'licenses/MIT.txt'.
|
|||
|
//
|
|||
|
using System;
|
|||
|
using Antmicro.Renode.Peripherals.Bus;
|
|||
|
using Antmicro.Renode.Logging;
|
|||
|
using Antmicro.Renode.Core.Structure;
|
|||
|
using System.Collections.Generic;
|
|||
|
using Antmicro.Renode.Core;
|
|||
|
using Antmicro.Renode.Core.Structure.Registers;
|
|||
|
|
|||
|
namespace Antmicro.Renode.Peripherals.SPI
|
|||
|
{
|
|||
|
public sealed class STM32SPI : NullRegistrationPointPeripheralContainer<ISPIPeripheral>, IWordPeripheral, IDoubleWordPeripheral, IBytePeripheral, IKnownSize
|
|||
|
{
|
|||
|
public STM32SPI(Machine machine) : base(machine)
|
|||
|
{
|
|||
|
receiveBuffer = new Queue<byte>();
|
|||
|
IRQ = new GPIO();
|
|||
|
SetupRegisters();
|
|||
|
Reset();
|
|||
|
}
|
|||
|
|
|||
|
public byte ReadByte(long offset)
|
|||
|
{
|
|||
|
// byte interface is there for DMA
|
|||
|
if(offset % 4 == 0)
|
|||
|
{
|
|||
|
return (byte)ReadDoubleWord(offset);
|
|||
|
}
|
|||
|
this.LogUnhandledRead(offset);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
public void WriteByte(long offset, byte value)
|
|||
|
{
|
|||
|
if(offset % 4 == 0)
|
|||
|
{
|
|||
|
WriteDoubleWord(offset, (uint)value);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
this.LogUnhandledWrite(offset, value);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public ushort ReadWord(long offset)
|
|||
|
{
|
|||
|
return (ushort)ReadDoubleWord(offset);
|
|||
|
}
|
|||
|
|
|||
|
public void WriteWord(long offset, ushort value)
|
|||
|
{
|
|||
|
WriteDoubleWord(offset, (uint)value);
|
|||
|
}
|
|||
|
|
|||
|
public uint ReadDoubleWord(long offset)
|
|||
|
{
|
|||
|
switch((Registers)offset)
|
|||
|
{
|
|||
|
case Registers.Data:
|
|||
|
return HandleDataRead();
|
|||
|
default:
|
|||
|
return registers.Read(offset);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void WriteDoubleWord(long offset, uint value)
|
|||
|
{
|
|||
|
switch((Registers)offset)
|
|||
|
{
|
|||
|
case Registers.Data:
|
|||
|
HandleDataWrite(value);
|
|||
|
break;
|
|||
|
default:
|
|||
|
registers.Write(offset, value);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public override void Reset()
|
|||
|
{
|
|||
|
lock(receiveBuffer)
|
|||
|
{
|
|||
|
receiveBuffer.Clear();
|
|||
|
}
|
|||
|
registers.Reset();
|
|||
|
}
|
|||
|
|
|||
|
public long Size
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return 0x400;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public GPIO IRQ
|
|||
|
{
|
|||
|
get;
|
|||
|
private set;
|
|||
|
}
|
|||
|
|
|||
|
private uint HandleDataRead()
|
|||
|
{
|
|||
|
IRQ.Unset();
|
|||
|
lock(receiveBuffer)
|
|||
|
{
|
|||
|
if(receiveBuffer.Count > 0)
|
|||
|
{
|
|||
|
var value = receiveBuffer.Dequeue();
|
|||
|
return value; // TODO: verify if Update should be called
|
|||
|
}
|
|||
|
this.Log(LogLevel.Warning, "Trying to read data register while no data has been received.");
|
|||
|
return 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void HandleDataWrite(uint value)
|
|||
|
{
|
|||
|
IRQ.Unset();
|
|||
|
lock(receiveBuffer)
|
|||
|
{
|
|||
|
var peripheral = RegisteredPeripheral;
|
|||
|
if(peripheral == null)
|
|||
|
{
|
|||
|
this.Log(LogLevel.Warning, "SPI transmission while no SPI peripheral is connected.");
|
|||
|
receiveBuffer.Enqueue(0x0);
|
|||
|
return;
|
|||
|
}
|
|||
|
receiveBuffer.Enqueue(peripheral.Transmit((byte)value)); // currently byte mode is the only one we support
|
|||
|
this.NoisyLog("Transmitted 0x{0:X}, received 0x{1:X}.", value, receiveBuffer.Peek());
|
|||
|
}
|
|||
|
Update();
|
|||
|
}
|
|||
|
|
|||
|
private void Update()
|
|||
|
{
|
|||
|
// TODO: verify this condition
|
|||
|
IRQ.Set(txBufferEmptyInterruptEnable.Value || rxBufferNotEmptyInterruptEnable.Value || txDmaEnable.Value || rxDmaEnable.Value);
|
|||
|
}
|
|||
|
|
|||
|
private void SetupRegisters()
|
|||
|
{
|
|||
|
var control2 = new DoubleWordRegister(this);
|
|||
|
txBufferEmptyInterruptEnable = control2.DefineFlagField(7);
|
|||
|
rxBufferNotEmptyInterruptEnable = control2.DefineFlagField(6);
|
|||
|
txDmaEnable = control2.DefineFlagField(1);
|
|||
|
rxDmaEnable = control2.DefineFlagField(0, writeCallback: (_,__) => Update());
|
|||
|
|
|||
|
var registerDictionary = new Dictionary<long, DoubleWordRegister>
|
|||
|
{
|
|||
|
{ (long)Registers.Control1, new DoubleWordRegister(this).WithValueField(3,3, name:"Baud").WithFlag(2, name:"Master")
|
|||
|
.WithFlag(8, name:"SSI").WithFlag(9, name:"SSM").WithFlag(6, changeCallback: (oldValue, newValue) => {
|
|||
|
if(!newValue)
|
|||
|
{
|
|||
|
IRQ.Unset();
|
|||
|
}
|
|||
|
}, name:"SpiEnable")},
|
|||
|
{(long)Registers.Status, new DoubleWordRegister(this, 2).WithFlag(1, FieldMode.Read, name:"TXE").WithFlag(0, FieldMode.Read, valueProviderCallback: _ => receiveBuffer.Count != 0 , name:"RXNE")},
|
|||
|
{(long)Registers.CRCPolynomial, new DoubleWordRegister(this, 7).WithValueField(0, 16, name:"CRCPoly") },
|
|||
|
{(long)Registers.I2SConfiguration, new DoubleWordRegister(this, 0).WithFlag(10, FieldMode.Read | FieldMode.WriteOneToClear, writeCallback: (oldValue, newValue) => {
|
|||
|
// write one to clear to keep this bit 0
|
|||
|
if(newValue)
|
|||
|
{
|
|||
|
this.Log(LogLevel.Warning, "Trying to enable not supported I2S mode.");
|
|||
|
}
|
|||
|
}, name:"I2SE")},
|
|||
|
{ (long)Registers.Control2, control2 }
|
|||
|
};
|
|||
|
registers = new DoubleWordRegisterCollection(this, registerDictionary);
|
|||
|
}
|
|||
|
|
|||
|
private DoubleWordRegisterCollection registers;
|
|||
|
private IFlagRegisterField txBufferEmptyInterruptEnable, rxBufferNotEmptyInterruptEnable, txDmaEnable, rxDmaEnable;
|
|||
|
|
|||
|
private readonly Queue<byte> receiveBuffer;
|
|||
|
|
|||
|
private enum Registers
|
|||
|
{
|
|||
|
Control1 = 0x0, // SPI_CR1,
|
|||
|
Control2 = 0x4, // SPI_CR2
|
|||
|
Status = 0x8, // SPI_SR
|
|||
|
Data = 0xC, // SPI_DR
|
|||
|
CRCPolynomial = 0x10, // SPI_CRCPR
|
|||
|
I2SConfiguration = 0x1C // SPI_I2SCFGR
|
|||
|
}
|
|||
|
}
|
|||
|
}
|