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_spi/chip_selector.h"
27 #include "pw_spi/device.h"
28 #include "pw_spi/initiator.h"
29 #include "pw_status/try.h"
30
31 namespace pw::spi {
32
~LinuxInitiator()33 LinuxInitiator::~LinuxInitiator() {
34 if (fd_ >= 0) {
35 close(fd_);
36 }
37 }
38
Configure(const Config & config)39 Status LinuxInitiator::Configure(const Config& config) {
40 // Map clock polarity/phase to Linux userspace equivalents
41 uint32_t mode = 0;
42 if (config.polarity == ClockPolarity::kActiveLow) {
43 mode |= SPI_CPOL; // Clock polarity -- signal is high when idle
44 }
45 if (config.phase == ClockPhase::kFallingEdge) {
46 mode |= SPI_CPHA; // Clock phase -- latch on falling edge
47 }
48 if (ioctl(fd_, SPI_IOC_WR_MODE32, &mode) < 0) {
49 PW_LOG_ERROR("Unable to set SPI mode");
50 return Status::InvalidArgument();
51 }
52
53 // Configure LSB/MSB first
54 uint8_t lsb_first = 0;
55 if (config.bit_order == BitOrder::kLsbFirst) {
56 lsb_first = 1; // non-zero value indicates LSB first
57 }
58 if (ioctl(fd_, SPI_IOC_WR_LSB_FIRST, &lsb_first) < 0) {
59 PW_LOG_ERROR("Unable to set SPI LSB");
60 return Status::InvalidArgument();
61 }
62
63 // Configure bits-per-word
64 uint8_t bits_per_word = config.bits_per_word();
65 if (ioctl(fd_, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word) < 0) {
66 PW_LOG_ERROR("Unable to set SPI Bits Per Word");
67 return Status::InvalidArgument();
68 }
69
70 // Configure maximum bus speed
71 if (ioctl(fd_, SPI_IOC_WR_MAX_SPEED_HZ, &max_speed_hz_) < 0) {
72 PW_LOG_ERROR("Unable to set SPI Max Speed");
73 return Status::InvalidArgument();
74 }
75
76 return OkStatus();
77 }
78
WriteRead(ConstByteSpan write_buffer,ByteSpan read_buffer)79 Status LinuxInitiator::WriteRead(ConstByteSpan write_buffer,
80 ByteSpan read_buffer) {
81 // Configure a full-duplex transfer using ioctl()
82 struct spi_ioc_transfer transaction[2];
83 memset(transaction, 0, sizeof(transaction));
84 transaction[0].tx_buf = reinterpret_cast<uintptr_t>(write_buffer.data());
85 transaction[0].len = write_buffer.size();
86
87 transaction[1].rx_buf = reinterpret_cast<uintptr_t>(read_buffer.data());
88 transaction[1].len = read_buffer.size();
89
90 if (ioctl(fd_, SPI_IOC_MESSAGE(2), transaction) < 0) {
91 PW_LOG_ERROR("Unable to perform SPI transfer");
92 return Status::Unknown();
93 }
94
95 return OkStatus();
96 }
97
SetActive(bool)98 Status LinuxChipSelector::SetActive(bool /*active*/) {
99 // Note: For Linux' SPI userspace support, chip-select control is not exposed
100 // directly to the user. This limits our ability to use the SPI HAL to do
101 // composite (multi read-write) transactions with the PW SPI HAL, as Linux
102 // performs composite transactions with a single ioctl() call using an array
103 // of descriptors provided as a parameter -- there's no way of separating
104 // individual operations from userspace. This could be addressed with a
105 // direct "Composite" transaction HAL API, or by using a raw GPIO
106 // to control of chip select from userspace (which is not common practice).
107 return OkStatus();
108 }
109
110 } // namespace pw::spi
111