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