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