// Copyright 2021 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_linux/spi.h" #include #include #include #include #include #include #include "pw_log/log.h" #include "pw_status/try.h" namespace pw::spi { LinuxInitiator::~LinuxInitiator() { if (fd_ >= 0) { close(fd_); } } Status LinuxInitiator::DoConfigure(const Config& config) { if (current_config_ == config) { // Don't waste time issuing ioctls if the config is not actually changing. return OkStatus(); } // Map clock polarity/phase to Linux userspace equivalents uint32_t mode = 0; if (config.polarity == ClockPolarity::kActiveLow) { mode |= SPI_CPOL; // Clock polarity -- signal is high when idle } if (config.phase == ClockPhase::kFallingEdge) { mode |= SPI_CPHA; // Clock phase -- latch on falling edge } if (ioctl(fd_, SPI_IOC_WR_MODE32, &mode) < 0) { PW_LOG_ERROR("Unable to set SPI mode"); return Status::InvalidArgument(); } // Configure LSB/MSB first uint8_t lsb_first = 0; if (config.bit_order == BitOrder::kLsbFirst) { lsb_first = 1; // non-zero value indicates LSB first } if (ioctl(fd_, SPI_IOC_WR_LSB_FIRST, &lsb_first) < 0) { PW_LOG_ERROR("Unable to set SPI LSB"); return Status::InvalidArgument(); } // Configure bits-per-word uint8_t bits_per_word = config.bits_per_word(); if (ioctl(fd_, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word) < 0) { PW_LOG_ERROR("Unable to set SPI Bits Per Word"); return Status::InvalidArgument(); } // Configure maximum bus speed if (ioctl(fd_, SPI_IOC_WR_MAX_SPEED_HZ, &max_speed_hz_) < 0) { PW_LOG_ERROR("Unable to set SPI Max Speed"); return Status::InvalidArgument(); } current_config_ = config; return OkStatus(); } Status LinuxInitiator::DoWriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer) { // Configure a full-duplex transfer using ioctl() // Neither read nor write: invalid. if (read_buffer.empty() && write_buffer.empty()) { return Status::InvalidArgument(); } struct spi_ioc_transfer transaction[2] = {}; unsigned long request = SPI_IOC_MESSAGE(1); // macro arg must be constant if (read_buffer.empty()) { // Write-only. transaction[0].tx_buf = reinterpret_cast(write_buffer.data()); transaction[0].len = write_buffer.size(); } else if (write_buffer.empty()) { // Read-only. transaction[0].rx_buf = reinterpret_cast(read_buffer.data()); transaction[0].len = read_buffer.size(); } else { // Read+Write. // The common read+write amount. const size_t common_len = std::min(write_buffer.size(), read_buffer.size()); transaction[0].tx_buf = reinterpret_cast(write_buffer.data()); transaction[0].rx_buf = reinterpret_cast(read_buffer.data()); transaction[0].len = common_len; // Handle different-sized buffers with a compound transaction if (write_buffer.size() > common_len) { auto write_remainder = write_buffer.subspan(common_len); transaction[1].tx_buf = reinterpret_cast(write_remainder.data()); transaction[1].len = write_remainder.size(); request = SPI_IOC_MESSAGE(2); } else if (read_buffer.size() > common_len) { auto read_remainder = read_buffer.subspan(common_len); transaction[1].rx_buf = reinterpret_cast(read_remainder.data()); transaction[1].len = read_remainder.size(); request = SPI_IOC_MESSAGE(2); } } if (ioctl(fd_, request, transaction) < 0) { PW_LOG_ERROR("Unable to perform SPI transfer"); return Status::Unknown(); } return OkStatus(); } Status LinuxChipSelector::SetActive(bool /*active*/) { // Note: For Linux' SPI userspace support, chip-select control is not exposed // directly to the user. This limits our ability to use the SPI HAL to do // composite (multi read-write) transactions with the PW SPI HAL, as Linux // performs composite transactions with a single ioctl() call using an array // of descriptors provided as a parameter -- there's no way of separating // individual operations from userspace. This could be addressed with a // direct "Composite" transaction HAL API, or by using a raw GPIO // to control of chip select from userspace (which is not common practice). return OkStatus(); } } // namespace pw::spi