// 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_spi/chip_selector.h" #include "pw_spi/device.h" #include "pw_spi/initiator.h" #include "pw_status/try.h" namespace pw::spi { LinuxInitiator::~LinuxInitiator() { if (fd_ >= 0) { close(fd_); } } Status LinuxInitiator::LazyInit() { if (fd_ >= 0) { return OkStatus(); } fd_ = open(path_, O_RDWR | O_EXCL); if (fd_ < 0) { PW_LOG_ERROR("Unable to open SPI device %s for read/write", path_); return Status::Unavailable(); } return OkStatus(); } Status LinuxInitiator::Configure(const Config& config) { PW_TRY(LazyInit()); // 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(); } return OkStatus(); } Status LinuxInitiator::WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer) { PW_TRY(LazyInit()); // Configure a full-duplex transfer using ioctl() struct spi_ioc_transfer transaction[2]; memset(transaction, 0, sizeof(transaction)); transaction[0].tx_buf = reinterpret_cast(write_buffer.data()); transaction[0].len = write_buffer.size(); transaction[1].rx_buf = reinterpret_cast(read_buffer.data()); transaction[1].len = read_buffer.size(); if (ioctl(fd_, SPI_IOC_MESSAGE(2), 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