125 lines
3.9 KiB
C++
125 lines
3.9 KiB
C++
// 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 <fcntl.h>
|
|
#include <linux/spi/spidev.h>
|
|
#include <linux/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
|
|
#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<uintptr_t>(write_buffer.data());
|
|
transaction[0].len = write_buffer.size();
|
|
|
|
transaction[1].rx_buf = reinterpret_cast<uintptr_t>(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
|