• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_spi_linux/spi.h"
16 
17 #include <fcntl.h>
18 #include <linux/spi/spidev.h>
19 #include <linux/types.h>
20 #include <sys/ioctl.h>
21 #include <unistd.h>
22 
23 #include <cstring>
24 
25 #include "pw_log/log.h"
26 #include "pw_status/try.h"
27 
28 namespace pw::spi {
29 
~LinuxInitiator()30 LinuxInitiator::~LinuxInitiator() {
31   if (fd_ >= 0) {
32     close(fd_);
33   }
34 }
35 
Configure(const Config & config)36 Status LinuxInitiator::Configure(const Config& config) {
37   // Map clock polarity/phase to Linux userspace equivalents
38   uint32_t mode = 0;
39   if (config.polarity == ClockPolarity::kActiveLow) {
40     mode |= SPI_CPOL;  // Clock polarity -- signal is high when idle
41   }
42   if (config.phase == ClockPhase::kFallingEdge) {
43     mode |= SPI_CPHA;  // Clock phase -- latch on falling edge
44   }
45   if (ioctl(fd_, SPI_IOC_WR_MODE32, &mode) < 0) {
46     PW_LOG_ERROR("Unable to set SPI mode");
47     return Status::InvalidArgument();
48   }
49 
50   // Configure LSB/MSB first
51   uint8_t lsb_first = 0;
52   if (config.bit_order == BitOrder::kLsbFirst) {
53     lsb_first = 1;  // non-zero value indicates LSB first
54   }
55   if (ioctl(fd_, SPI_IOC_WR_LSB_FIRST, &lsb_first) < 0) {
56     PW_LOG_ERROR("Unable to set SPI LSB");
57     return Status::InvalidArgument();
58   }
59 
60   // Configure bits-per-word
61   uint8_t bits_per_word = config.bits_per_word();
62   if (ioctl(fd_, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word) < 0) {
63     PW_LOG_ERROR("Unable to set SPI Bits Per Word");
64     return Status::InvalidArgument();
65   }
66 
67   // Configure maximum bus speed
68   if (ioctl(fd_, SPI_IOC_WR_MAX_SPEED_HZ, &max_speed_hz_) < 0) {
69     PW_LOG_ERROR("Unable to set SPI Max Speed");
70     return Status::InvalidArgument();
71   }
72 
73   return OkStatus();
74 }
75 
WriteRead(ConstByteSpan write_buffer,ByteSpan read_buffer)76 Status LinuxInitiator::WriteRead(ConstByteSpan write_buffer,
77                                  ByteSpan read_buffer) {
78   // Configure a full-duplex transfer using ioctl()
79 
80   struct spi_ioc_transfer transaction[2] = {};
81   unsigned long request = SPI_IOC_MESSAGE(1);  // macro arg must be constant
82   const size_t common_len = std::min(write_buffer.size(), read_buffer.size());
83 
84   transaction[0].tx_buf = reinterpret_cast<uintptr_t>(write_buffer.data());
85   transaction[0].rx_buf = reinterpret_cast<uintptr_t>(read_buffer.data());
86   transaction[0].len = common_len;
87 
88   // Handle different-sized buffers with a compound transaction
89   if (write_buffer.size() > common_len) {
90     auto write_remainder = write_buffer.subspan(common_len);
91     transaction[1].tx_buf = reinterpret_cast<uintptr_t>(write_remainder.data());
92     transaction[1].len = write_remainder.size();
93     request = SPI_IOC_MESSAGE(2);
94   } else if (read_buffer.size() > common_len) {
95     auto read_remainder = read_buffer.subspan(common_len);
96     transaction[1].rx_buf = reinterpret_cast<uintptr_t>(read_remainder.data());
97     transaction[1].len = read_remainder.size();
98     request = SPI_IOC_MESSAGE(2);
99   }
100 
101   if (ioctl(fd_, request, transaction) < 0) {
102     PW_LOG_ERROR("Unable to perform SPI transfer");
103     return Status::Unknown();
104   }
105 
106   return OkStatus();
107 }
108 
SetActive(bool)109 Status LinuxChipSelector::SetActive(bool /*active*/) {
110   // Note: For Linux' SPI userspace support, chip-select control is not exposed
111   // directly to the user.  This limits our ability to use the SPI HAL to do
112   // composite (multi read-write) transactions with the PW SPI HAL, as Linux
113   // performs composite transactions with a single ioctl() call using an array
114   // of descriptors provided as a parameter -- there's no way of separating
115   // individual operations from userspace.  This could be addressed with a
116   // direct "Composite" transaction HAL API, or by using a raw GPIO
117   // to control of chip select from userspace (which is not common practice).
118   return OkStatus();
119 }
120 
121 }  // namespace pw::spi
122