|  | // Copyright 2022 The Pigweed Authors | 
|  | // | 
|  | // 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. | 
|  |  | 
|  | #include "pw_spi_stm32cube/initiator.h" | 
|  |  | 
|  | #include <algorithm> | 
|  |  | 
|  | #include "pw_log/log.h" | 
|  | #include "pw_status/try.h" | 
|  | #include "stm32cube/stm32cube.h" | 
|  |  | 
|  | namespace pw::spi { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | constexpr uint32_t kTimeout = 10000; | 
|  |  | 
|  | constexpr Status ConvertStatus(HAL_StatusTypeDef status) { | 
|  | switch (status) { | 
|  | case HAL_OK: | 
|  | return OkStatus(); | 
|  | case HAL_ERROR: | 
|  | return Status::Internal(); | 
|  | case HAL_BUSY: | 
|  | return Status::Unavailable(); | 
|  | case HAL_TIMEOUT: | 
|  | return Status::DeadlineExceeded(); | 
|  | } | 
|  | return Status::NotFound();  // Unreachable. | 
|  | } | 
|  |  | 
|  | uint32_t GetDataSize(BitsPerWord bits_per_word) { | 
|  | if (bits_per_word() == 8) { | 
|  | return SPI_DATASIZE_8BIT; | 
|  | } else if (bits_per_word() == 16) { | 
|  | return SPI_DATASIZE_16BIT; | 
|  | } | 
|  | PW_UNREACHABLE; | 
|  | return SPI_DATASIZE_8BIT; | 
|  | } | 
|  |  | 
|  | constexpr uint32_t GetBitOrder(BitOrder bit_order) { | 
|  | switch (bit_order) { | 
|  | case BitOrder::kLsbFirst: | 
|  | return SPI_FIRSTBIT_LSB; | 
|  | case BitOrder::kMsbFirst: | 
|  | return SPI_FIRSTBIT_MSB; | 
|  | } | 
|  | PW_UNREACHABLE; | 
|  | return SPI_FIRSTBIT_MSB; | 
|  | } | 
|  |  | 
|  | constexpr uint32_t GetPhase(ClockPhase phase) { | 
|  | switch (phase) { | 
|  | case ClockPhase::kFallingEdge: | 
|  | return SPI_PHASE_1EDGE; | 
|  | case ClockPhase::kRisingEdge: | 
|  | return SPI_PHASE_2EDGE; | 
|  | } | 
|  | PW_UNREACHABLE; | 
|  | return SPI_PHASE_1EDGE; | 
|  | } | 
|  |  | 
|  | constexpr uint32_t GetPolarity(ClockPolarity polarity) { | 
|  | switch (polarity) { | 
|  | case ClockPolarity::kActiveHigh: | 
|  | return SPI_POLARITY_HIGH; | 
|  | case ClockPolarity::kActiveLow: | 
|  | return SPI_POLARITY_LOW; | 
|  | } | 
|  | PW_UNREACHABLE; | 
|  | return SPI_POLARITY_HIGH; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | Stm32CubeInitiator::Stm32CubeInitiator() | 
|  | : spi_handle{.Instance = SPI5, | 
|  | .Init{.Mode = SPI_MODE_MASTER, | 
|  | .Direction = SPI_DIRECTION_2LINES, | 
|  | .DataSize = SPI_DATASIZE_8BIT, | 
|  | .CLKPolarity = SPI_POLARITY_LOW, | 
|  | .CLKPhase = SPI_PHASE_1EDGE, | 
|  | .NSS = SPI_NSS_SOFT, | 
|  | .BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2, | 
|  | .FirstBit = SPI_FIRSTBIT_MSB, | 
|  | .TIMode = SPI_TIMODE_DISABLE, | 
|  | .CRCCalculation = SPI_CRCCALCULATION_DISABLE, | 
|  | .CRCPolynomial = 7}} {} | 
|  |  | 
|  | Stm32CubeInitiator::~Stm32CubeInitiator() = default; | 
|  |  | 
|  | Status Stm32CubeInitiator::LazyInit() { | 
|  | if (initialized) | 
|  | return init_status; | 
|  | init_status = InitSPI(); | 
|  | initialized = true; | 
|  | PW_LOG_INFO("Stm32CubeInitiator::LazyInit: %s", init_status.str()); | 
|  | return init_status; | 
|  | } | 
|  |  | 
|  | Status Stm32CubeInitiator::InitSPI() { | 
|  | auto s = HAL_SPI_Init(&spi_handle); | 
|  | auto status = ConvertStatus(s); | 
|  | PW_LOG_INFO("HAL_SPI_Init =>: %s", status.str()); | 
|  | return status; | 
|  | } | 
|  |  | 
|  | Status Stm32CubeInitiator::Configure(const Config& config) { | 
|  | spi_handle.Init.DataSize = GetDataSize(config.bits_per_word); | 
|  | spi_handle.Init.FirstBit = GetBitOrder(config.bit_order); | 
|  | spi_handle.Init.CLKPhase = GetPhase(config.phase); | 
|  | spi_handle.Init.CLKPolarity = GetPolarity(config.polarity); | 
|  | PW_TRY(LazyInit()); | 
|  |  | 
|  | return OkStatus(); | 
|  | } | 
|  |  | 
|  | Status Stm32CubeInitiator::WriteRead(ConstByteSpan write_buffer, | 
|  | ByteSpan read_buffer) { | 
|  | PW_TRY(LazyInit()); | 
|  |  | 
|  | HAL_StatusTypeDef status; | 
|  |  | 
|  | if (!write_buffer.empty()) { | 
|  | if (!read_buffer.empty()) { | 
|  | // TODO(cmumford): Not yet conforming to the WriteRead contract. | 
|  | uint16_t size = std::min(write_buffer.size(), read_buffer.size()); | 
|  | status = HAL_SPI_TransmitReceive( | 
|  | &spi_handle, | 
|  | reinterpret_cast<uint8_t*>( | 
|  | const_cast<std::byte*>(write_buffer.data())), | 
|  | reinterpret_cast<uint8_t*>(read_buffer.data()), | 
|  | size, | 
|  | kTimeout); | 
|  | } else { | 
|  | status = | 
|  | HAL_SPI_Transmit(&spi_handle, | 
|  | reinterpret_cast<uint8_t*>( | 
|  | const_cast<std::byte*>(write_buffer.data())), | 
|  | write_buffer.size(), | 
|  | kTimeout); | 
|  | if (status != HAL_OK) { | 
|  | PW_LOG_ERROR("Stm32CubeInitiator::WriteRead: write:%ld B, s:%s", | 
|  | write_buffer.size(), | 
|  | ConvertStatus(status).str()); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | status = HAL_SPI_Receive(&spi_handle, | 
|  | reinterpret_cast<uint8_t*>(read_buffer.data()), | 
|  | read_buffer.size(), | 
|  | kTimeout); | 
|  | } | 
|  |  | 
|  | return ConvertStatus(status); | 
|  | } | 
|  |  | 
|  | }  // namespace pw::spi |