xref: /aosp_15_r20/external/pigweed/pw_spi_linux/spi.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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 
DoConfigure(const Config & config)36 Status LinuxInitiator::DoConfigure(const Config& config) {
37   if (current_config_ == config) {
38     // Don't waste time issuing ioctls if the config is not actually changing.
39     return OkStatus();
40   }
41 
42   // Map clock polarity/phase to Linux userspace equivalents
43   uint32_t mode = 0;
44   if (config.polarity == ClockPolarity::kActiveLow) {
45     mode |= SPI_CPOL;  // Clock polarity -- signal is high when idle
46   }
47   if (config.phase == ClockPhase::kFallingEdge) {
48     mode |= SPI_CPHA;  // Clock phase -- latch on falling edge
49   }
50   if (ioctl(fd_, SPI_IOC_WR_MODE32, &mode) < 0) {
51     PW_LOG_ERROR("Unable to set SPI mode");
52     return Status::InvalidArgument();
53   }
54 
55   // Configure LSB/MSB first
56   uint8_t lsb_first = 0;
57   if (config.bit_order == BitOrder::kLsbFirst) {
58     lsb_first = 1;  // non-zero value indicates LSB first
59   }
60   if (ioctl(fd_, SPI_IOC_WR_LSB_FIRST, &lsb_first) < 0) {
61     PW_LOG_ERROR("Unable to set SPI LSB");
62     return Status::InvalidArgument();
63   }
64 
65   // Configure bits-per-word
66   uint8_t bits_per_word = config.bits_per_word();
67   if (ioctl(fd_, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word) < 0) {
68     PW_LOG_ERROR("Unable to set SPI Bits Per Word");
69     return Status::InvalidArgument();
70   }
71 
72   // Configure maximum bus speed
73   if (ioctl(fd_, SPI_IOC_WR_MAX_SPEED_HZ, &max_speed_hz_) < 0) {
74     PW_LOG_ERROR("Unable to set SPI Max Speed");
75     return Status::InvalidArgument();
76   }
77 
78   current_config_ = config;
79   return OkStatus();
80 }
81 
DoWriteRead(ConstByteSpan write_buffer,ByteSpan read_buffer)82 Status LinuxInitiator::DoWriteRead(ConstByteSpan write_buffer,
83                                    ByteSpan read_buffer) {
84   // Configure a full-duplex transfer using ioctl()
85 
86   // Neither read nor write: invalid.
87   if (read_buffer.empty() && write_buffer.empty()) {
88     return Status::InvalidArgument();
89   }
90 
91   struct spi_ioc_transfer transaction[2] = {};
92   unsigned long request = SPI_IOC_MESSAGE(1);  // macro arg must be constant
93 
94   if (read_buffer.empty()) {
95     // Write-only.
96     transaction[0].tx_buf = reinterpret_cast<uintptr_t>(write_buffer.data());
97     transaction[0].len = write_buffer.size();
98   } else if (write_buffer.empty()) {
99     // Read-only.
100     transaction[0].rx_buf = reinterpret_cast<uintptr_t>(read_buffer.data());
101     transaction[0].len = read_buffer.size();
102   } else {
103     // Read+Write.
104 
105     // The common read+write amount.
106     const size_t common_len = std::min(write_buffer.size(), read_buffer.size());
107     transaction[0].tx_buf = reinterpret_cast<uintptr_t>(write_buffer.data());
108     transaction[0].rx_buf = reinterpret_cast<uintptr_t>(read_buffer.data());
109     transaction[0].len = common_len;
110 
111     // Handle different-sized buffers with a compound transaction
112     if (write_buffer.size() > common_len) {
113       auto write_remainder = write_buffer.subspan(common_len);
114       transaction[1].tx_buf =
115           reinterpret_cast<uintptr_t>(write_remainder.data());
116       transaction[1].len = write_remainder.size();
117       request = SPI_IOC_MESSAGE(2);
118     } else if (read_buffer.size() > common_len) {
119       auto read_remainder = read_buffer.subspan(common_len);
120       transaction[1].rx_buf =
121           reinterpret_cast<uintptr_t>(read_remainder.data());
122       transaction[1].len = read_remainder.size();
123       request = SPI_IOC_MESSAGE(2);
124     }
125   }
126 
127   if (ioctl(fd_, request, transaction) < 0) {
128     PW_LOG_ERROR("Unable to perform SPI transfer");
129     return Status::Unknown();
130   }
131 
132   return OkStatus();
133 }
134 
SetActive(bool)135 Status LinuxChipSelector::SetActive(bool /*active*/) {
136   // Note: For Linux' SPI userspace support, chip-select control is not exposed
137   // directly to the user.  This limits our ability to use the SPI HAL to do
138   // composite (multi read-write) transactions with the PW SPI HAL, as Linux
139   // performs composite transactions with a single ioctl() call using an array
140   // of descriptors provided as a parameter -- there's no way of separating
141   // individual operations from userspace.  This could be addressed with a
142   // direct "Composite" transaction HAL API, or by using a raw GPIO
143   // to control of chip select from userspace (which is not common practice).
144   return OkStatus();
145 }
146 
147 }  // namespace pw::spi
148