You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

546 lines
16 KiB

/*
* upstream_spi.c
*
* Created on: 21/06/2015
* Author: Robert Fisk
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "upstream_interface_def.h"
#include "upstream_spi.h"
#include "stm32f4xx_hal.h"
#include "board_config.h"
#include "interrupts.h"
SPI_HandleTypeDef Hspi1;
UpstreamPacketTypeDef UpstreamPacket0;
UpstreamPacketTypeDef UpstreamPacket1;
UpstreamPacketTypeDef* CurrentWorkingPacket;
UpstreamPacketTypeDef* NextTxPacket = NULL; //Indicates we have a pending TX packet
InterfaceStateTypeDef UpstreamInterfaceState = UPSTREAM_INTERFACE_IDLE;
FreePacketCallbackTypeDef PendingFreePacketCallback = NULL; //Indicates someone is waiting for a packet buffer to become available
SpiPacketReceivedCallbackTypeDef ReceivePacketCallback = NULL; //Indicates someone is waiting for a received packet
uint32_t TemporaryIncomingPacketLength; //We don't actually care about what Downstream sends us when we are transmitting. We just need somewhere to put it so that our own packet length is not overwritten.
uint8_t TxOkInterruptReceived = 0;
uint8_t SentCommandClass;
uint8_t SentCommand;
void Upstream_BeginTransmitPacketSize(void);
void Upstream_BeginTransmitPacketBody(void);
HAL_StatusTypeDef Upstream_CheckBeginPacketReception(void);
void Upstream_BeginReceivePacketSize(UpstreamPacketTypeDef* freePacket);
void Upstream_BeginReceivePacketBody(void);
void Upstream_InitSPI(void)
{
UpstreamPacket0.Busy = NOT_BUSY;
UpstreamPacket1.Busy = NOT_BUSY;
Hspi1.Instance = SPI1;
Hspi1.State = HAL_SPI_STATE_RESET;
Hspi1.Init.Mode = SPI_MODE_MASTER;
Hspi1.Init.Direction = SPI_DIRECTION_2LINES;
Hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
Hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
Hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
Hspi1.Init.NSS = SPI_NSS_SOFT;
Hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; //42MHz APB2 / 4 = 10.5Mbaud
Hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
Hspi1.Init.TIMode = SPI_TIMODE_DISABLED;
Hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_ENABLED;
Hspi1.Init.CRCPolynomial = SPI_CRC_DEFAULTPOLYNOMIAL;
HAL_SPI_Init(&Hspi1);
if (DOWNSTREAM_TX_OK_ACTIVE)
{
TxOkInterruptReceived = 1;
}
}
//Used by USB interface classes, and by our internal RX code.
HAL_StatusTypeDef Upstream_GetFreePacket(FreePacketCallbackTypeDef callback)
{
if (UpstreamInterfaceState >= UPSTREAM_INTERFACE_ERROR)
{
return HAL_ERROR;
}
//Do we already have a queued callback?
if (PendingFreePacketCallback != NULL)
{
UPSTREAM_SPI_FREAKOUT;
return HAL_ERROR;
}
//Check if there is a free buffer now
if (UpstreamPacket0.Busy == NOT_BUSY)
{
UpstreamPacket0.Busy = BUSY;
callback(&UpstreamPacket0);
return HAL_OK;
}
if (UpstreamPacket1.Busy == NOT_BUSY)
{
UpstreamPacket1.Busy = BUSY;
callback(&UpstreamPacket1);
return HAL_OK;
}
//Otherwise save requested address for when a buffer becomes free in the future
PendingFreePacketCallback = callback;
return HAL_OK;
}
UpstreamPacketTypeDef* Upstream_GetFreePacketImmediately(void)
{
if (UpstreamInterfaceState >= UPSTREAM_INTERFACE_ERROR)
{
return NULL;
}
//We are expecting a free buffer now
if (UpstreamPacket0.Busy == NOT_BUSY)
{
UpstreamPacket0.Busy = BUSY;
return &UpstreamPacket0;
}
if (UpstreamPacket1.Busy == NOT_BUSY)
{
UpstreamPacket1.Busy = BUSY;
return &UpstreamPacket1;
}
//Should not happen:
UPSTREAM_SPI_FREAKOUT;
return NULL;
}
//Used by USB interface classes, and by our internal RX code.
void Upstream_ReleasePacket(UpstreamPacketTypeDef* packetToRelease)
{
FreePacketCallbackTypeDef tempCallback;
if (UpstreamInterfaceState >= UPSTREAM_INTERFACE_ERROR)
{
return;
}
if ((packetToRelease != &UpstreamPacket0) &&
(packetToRelease != &UpstreamPacket1))
{
UPSTREAM_SPI_FREAKOUT;
return;
}
if (packetToRelease->Busy != BUSY)
{
UPSTREAM_SPI_FREAKOUT;
return;
}
if (PendingFreePacketCallback != NULL)
{
tempCallback = PendingFreePacketCallback; //In extreme situations, running this callback can trigger another request for a free packet,
PendingFreePacketCallback = NULL; //thereby causing GetFreePacket to freak out. So we need to clear the callback indicator first.
tempCallback(packetToRelease);
}
else
{
packetToRelease->Busy = NOT_BUSY;
}
}
//Used by USB interface classes only.
//OK to call when still transmitting another packet.
//Not OK to call when receiving or waiting for downstream reply,
//as we can't let the size/packet sequence get out of sync.
HAL_StatusTypeDef Upstream_TransmitPacket(UpstreamPacketTypeDef* packetToWrite)
{
if (UpstreamInterfaceState >= UPSTREAM_INTERFACE_ERROR)
{
return HAL_ERROR;
}
//Sanity checks
if ((packetToWrite != &UpstreamPacket0) &&
(packetToWrite != &UpstreamPacket1))
{
UPSTREAM_SPI_FREAKOUT;
return HAL_ERROR;
}
if ((packetToWrite->Busy != BUSY) ||
(packetToWrite->Length16 < UPSTREAM_PACKET_LEN_MIN_16) ||
(packetToWrite->Length16 > UPSTREAM_PACKET_LEN_16))
{
UPSTREAM_SPI_FREAKOUT;
return HAL_ERROR;
}
if (NextTxPacket != NULL)
{
UPSTREAM_SPI_FREAKOUT;
return HAL_ERROR;
}
switch (UpstreamInterfaceState)
{
case UPSTREAM_INTERFACE_TX_SIZE_WAIT:
case UPSTREAM_INTERFACE_TX_SIZE:
case UPSTREAM_INTERFACE_TX_PACKET_WAIT:
case UPSTREAM_INTERFACE_TX_PACKET:
NextTxPacket = packetToWrite;
break;
case UPSTREAM_INTERFACE_IDLE:
UpstreamInterfaceState = UPSTREAM_INTERFACE_TX_SIZE_WAIT;
CurrentWorkingPacket = packetToWrite;
SentCommandClass = CurrentWorkingPacket->CommandClass;
SentCommand = CurrentWorkingPacket->Command;
//Downstream may have set TxOk pin before we wanted to transmit.
//In this case we can go ahead and transmit now.
if (TxOkInterruptReceived)
{
TxOkInterruptReceived = 0;
Upstream_BeginTransmitPacketSize();
}
break;
default:
UPSTREAM_SPI_FREAKOUT;
return HAL_ERROR;
}
return HAL_OK;
}
//Called at the end of the SPI TxRx transfer,
//at SPI1 interrupt priority. Assume *hspi points to our hspi1.
//We TxRx our outgoing packet because the SPI hardware freaks out if we only Tx it :-/
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
UNUSED(hspi);
SpiPacketReceivedCallbackTypeDef tempPacketCallback;
UpstreamPacketTypeDef* tempPacketToFree;
SPI1_NSS_DEASSERT;
if (UpstreamInterfaceState >= UPSTREAM_INTERFACE_ERROR)
{
return;
}
//Finished transmitting packet size
if (UpstreamInterfaceState == UPSTREAM_INTERFACE_TX_SIZE)
{
UpstreamInterfaceState = UPSTREAM_INTERFACE_TX_PACKET_WAIT;
if (TxOkInterruptReceived)
{
TxOkInterruptReceived = 0;
Upstream_BeginTransmitPacketBody();
}
return;
}
//Finished transmitting packet body
if (UpstreamInterfaceState == UPSTREAM_INTERFACE_TX_PACKET)
{
if ((PendingFreePacketCallback != NULL) && (NextTxPacket == NULL))
{
UPSTREAM_SPI_FREAKOUT;
return;
}
tempPacketToFree = CurrentWorkingPacket;
if (NextTxPacket != NULL)
{
//NextTxPacket has already passed the checks in Upstream_TransmitPacket.
//So we just need to pass it to HAL_SPI_Transmit_DMA.
UpstreamInterfaceState = UPSTREAM_INTERFACE_TX_SIZE_WAIT;
CurrentWorkingPacket = NextTxPacket;
NextTxPacket = NULL;
SentCommandClass = CurrentWorkingPacket->CommandClass;
SentCommand = CurrentWorkingPacket->Command;
if (TxOkInterruptReceived)
{
TxOkInterruptReceived = 0;
Upstream_BeginTransmitPacketSize();
}
}
else
{
//No packet queued for transmission:
UpstreamInterfaceState = UPSTREAM_INTERFACE_IDLE;
if (ReceivePacketCallback != NULL)
{
Upstream_CheckBeginPacketReception();
}
}
//Release old packet after moving Next to Current
Upstream_ReleasePacket(tempPacketToFree);
return;
}
if (UpstreamInterfaceState == UPSTREAM_INTERFACE_RX_SIZE)
{
if ((CurrentWorkingPacket->Length16 < UPSTREAM_PACKET_LEN_MIN_16) ||
(CurrentWorkingPacket->Length16 > UPSTREAM_PACKET_LEN_16))
{
UPSTREAM_SPI_FREAKOUT;
return;
}
UpstreamInterfaceState = UPSTREAM_INTERFACE_RX_PACKET_WAIT;
if (TxOkInterruptReceived)
{
TxOkInterruptReceived = 0;
Upstream_BeginReceivePacketBody();
}
return;
}
if (UpstreamInterfaceState == UPSTREAM_INTERFACE_RX_PACKET)
{
UpstreamInterfaceState = UPSTREAM_INTERFACE_IDLE;
if (ReceivePacketCallback == NULL)
{
UPSTREAM_SPI_FREAKOUT;
return;
}
if ((CurrentWorkingPacket->CommandClass == COMMAND_CLASS_ERROR) &&
(CurrentWorkingPacket->Command == COMMAND_ERROR_DEVICE_DISCONNECTED))
{
Upstream_ReleasePacket(CurrentWorkingPacket);
ReceivePacketCallback = NULL;
Upstream_StateMachine_DeviceDisconnected();
return;
}
if (((CurrentWorkingPacket->CommandClass & COMMAND_CLASS_MASK) != (SentCommandClass & COMMAND_CLASS_MASK)) ||
(CurrentWorkingPacket->Command != SentCommand))
{
UPSTREAM_SPI_FREAKOUT;
Upstream_ReleasePacket(CurrentWorkingPacket);
CurrentWorkingPacket = NULL; //Call back with a NULL packet to indicate error
}
//USB interface may want to receive another packet immediately,
//so clear ReceivePacketCallback before the call.
//It is the callback's responsibility to release the packet buffer we are passing to it!
tempPacketCallback = ReceivePacketCallback;
ReceivePacketCallback = NULL;
tempPacketCallback(CurrentWorkingPacket);
return;
}
//case default:
UPSTREAM_SPI_FREAKOUT;
}
//Used by USB interface classes.
//Ok to call when idle or transmitting.
//Not OK to call when receiving or waiting for downstream reply.
HAL_StatusTypeDef Upstream_ReceivePacket(SpiPacketReceivedCallbackTypeDef callback)
{
if (UpstreamInterfaceState >= UPSTREAM_INTERFACE_ERROR)
{
return HAL_ERROR;
}
if (ReceivePacketCallback != NULL)
{
UPSTREAM_SPI_FREAKOUT;
return HAL_ERROR;
}
ReceivePacketCallback = callback;
return Upstream_CheckBeginPacketReception();
}
//Internal use only.
HAL_StatusTypeDef Upstream_CheckBeginPacketReception(void)
{
if (UpstreamInterfaceState >= UPSTREAM_INTERFACE_ERROR)
{
return HAL_ERROR;
}
if (UpstreamInterfaceState >= UPSTREAM_INTERFACE_RX_SIZE_WAIT)
{
UPSTREAM_SPI_FREAKOUT;
return HAL_ERROR;
}
if (UpstreamInterfaceState == UPSTREAM_INTERFACE_IDLE)
{
UpstreamInterfaceState = UPSTREAM_INTERFACE_RX_SIZE_WAIT;
if (TxOkInterruptReceived)
{
TxOkInterruptReceived = 0;
Upstream_GetFreePacket(Upstream_BeginReceivePacketSize);
}
}
return HAL_OK;
}
//This is called by EXTI3 falling edge interrupt,
//indicating that downstream is ready for the next transaction.
void Upstream_TxOkInterrupt(void)
{
if (UpstreamInterfaceState >= UPSTREAM_INTERFACE_ERROR)
{
return;
}
switch (UpstreamInterfaceState)
{
case UPSTREAM_INTERFACE_IDLE:
TxOkInterruptReceived = 1;
break;
case UPSTREAM_INTERFACE_TX_SIZE_WAIT:
Upstream_BeginTransmitPacketSize();
break;
case UPSTREAM_INTERFACE_TX_PACKET_WAIT:
Upstream_BeginTransmitPacketBody();
break;
case UPSTREAM_INTERFACE_RX_SIZE_WAIT:
Upstream_GetFreePacket(Upstream_BeginReceivePacketSize);
break;
case UPSTREAM_INTERFACE_RX_PACKET_WAIT:
Upstream_BeginReceivePacketBody();
break;
default:
UPSTREAM_SPI_FREAKOUT;
}
}
void Upstream_BeginTransmitPacketSize(void)
{
UpstreamInterfaceState = UPSTREAM_INTERFACE_TX_SIZE;
SPI1_NSS_ASSERT;
if (HAL_SPI_TransmitReceive(&Hspi1,
(uint8_t*)&CurrentWorkingPacket->Length16,
(uint8_t*)&TemporaryIncomingPacketLength,
2, //We only need to write one word, but the peripheral library freaks out...
SPI_TIMEOUT_VALUE) != HAL_OK)
{
UPSTREAM_SPI_FREAKOUT;
}
HAL_SPI_TxRxCpltCallback(&Hspi1);
}
void Upstream_BeginTransmitPacketBody(void)
{
UpstreamInterfaceState = UPSTREAM_INTERFACE_TX_PACKET;
SPI1_NSS_ASSERT;
if (HAL_SPI_TransmitReceive_DMA(&Hspi1,
&CurrentWorkingPacket->CommandClass,
&CurrentWorkingPacket->CommandClass,
((CurrentWorkingPacket->Length16 < 2) ? 2 : CurrentWorkingPacket->Length16)) != HAL_OK)
{
UPSTREAM_SPI_FREAKOUT;
}
}
//Internal use only.
//Called when we want to receive downstream packet, and a packet buffer has become free.
void Upstream_BeginReceivePacketSize(UpstreamPacketTypeDef* freePacket)
{
if (UpstreamInterfaceState >= UPSTREAM_INTERFACE_ERROR)
{
return;
}
if (UpstreamInterfaceState != UPSTREAM_INTERFACE_RX_SIZE_WAIT)
{
UPSTREAM_SPI_FREAKOUT;
return;
}
UpstreamInterfaceState = UPSTREAM_INTERFACE_RX_SIZE;
CurrentWorkingPacket = freePacket;
CurrentWorkingPacket->Length16 = 0; //Our RX buffer is used by HAL_SPI_TransmitReceive_DMA as dummy TX data, we set Length to 0 so downstream will know this is a dummy packet.
SPI1_NSS_ASSERT;
if (HAL_SPI_TransmitReceive(&Hspi1,
(uint8_t*)&CurrentWorkingPacket->Length16,
(uint8_t*)&CurrentWorkingPacket->Length16,
2, //We only need to write one word, but the peripheral library freaks out...
SPI_TIMEOUT_VALUE) != HAL_OK)
{
UPSTREAM_SPI_FREAKOUT;
}
HAL_SPI_TxRxCpltCallback(&Hspi1);
}
void Upstream_BeginReceivePacketBody(void)
{
UpstreamInterfaceState = UPSTREAM_INTERFACE_RX_PACKET;
SPI1_NSS_ASSERT;
if (HAL_SPI_TransmitReceive_DMA(&Hspi1,
&CurrentWorkingPacket->CommandClass,
&CurrentWorkingPacket->CommandClass,
((CurrentWorkingPacket->Length16 < 2) ? 2 : CurrentWorkingPacket->Length16)) != HAL_OK)
{
UPSTREAM_SPI_FREAKOUT;
}
}
//Something bad happened! Possibly CRC error...
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
UNUSED(hspi);
SpiPacketReceivedCallbackTypeDef tempPacketCallback;
if (UpstreamInterfaceState >= UPSTREAM_INTERFACE_ERROR)
{
return;
}
UPSTREAM_SPI_FREAKOUT;
if (ReceivePacketCallback != NULL)
{
tempPacketCallback = ReceivePacketCallback;
ReceivePacketCallback = NULL;
tempPacketCallback(NULL); //Call back with a NULL packet to indicate error
}
}