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