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