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 #pragma once 16 17 #include <utility> 18 19 #include "pw_bytes/span.h" 20 #include "pw_spi/chip_selector.h" 21 #include "pw_spi/initiator.h" 22 #include "pw_status/status.h" 23 #include "pw_status/try.h" 24 #include "pw_sync/borrow.h" 25 26 namespace pw::spi { 27 28 // The Device class enables data transfer with a specific SPI responder. 29 // This class combines an Initiator (representing the physical SPI bus), its 30 // configuration data, and the ChipSelector object to uniquely address a device. 31 // Transfers to a selected initiator are guarded against concurrent access 32 // through the use of the `Borrowable` object. 33 class Device { 34 public: Device(sync::Borrowable<Initiator> initiator,const Config config,ChipSelector & selector)35 Device(sync::Borrowable<Initiator> initiator, 36 const Config config, 37 ChipSelector& selector) 38 : initiator_(initiator), config_(config), selector_(selector) {} 39 40 ~Device() = default; 41 42 // Synchronously read data from the SPI responder until the provided 43 // `read_buffer` is full. 44 // This call will configure the bus and activate/deactivate chip select 45 // for the transfer 46 // 47 // Note: This call will block in the event that other clients are currently 48 // performing transactions using the same SPI Initiator. 49 // Returns OkStatus() on success, and implementation-specific values on 50 // failure. Read(ByteSpan read_buffer)51 Status Read(ByteSpan read_buffer) { return WriteRead({}, read_buffer); } 52 53 // Synchronously write the contents of `write_buffer` to the SPI responder. 54 // This call will configure the bus and activate/deactivate chip select 55 // for the transfer 56 // 57 // Note: This call will block in the event that other clients are currently 58 // performing transactions using the same SPI Initiator. 59 // Returns OkStatus() on success, and implementation-specific values on 60 // failure. Write(ConstByteSpan write_buffer)61 Status Write(ConstByteSpan write_buffer) { 62 return WriteRead(write_buffer, {}); 63 } 64 65 // Perform a synchronous read/write transfer with the SPI responder. Data 66 // from the `write_buffer` object is written to the bus, while the 67 // `read_buffer` is populated with incoming data on the bus. In the event 68 // the read buffer is smaller than the write buffer (or zero-size), any 69 // additional input bytes are discarded. In the event the write buffer is 70 // smaller than the read buffer (or zero size), the output is padded with 71 // 0-bits for the remainder of the transfer. 72 // This call will configure the bus and activate/deactivate chip select 73 // for the transfer 74 // 75 // Note: This call will block in the event that other clients 76 // are currently performing transactions using the same SPI Initiator. 77 // Returns OkStatus() on success, and implementation-specific values on 78 // failure. WriteRead(ConstByteSpan write_buffer,ByteSpan read_buffer)79 Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer) { 80 return StartTransaction(ChipSelectBehavior::kPerWriteRead) 81 .WriteRead(write_buffer, read_buffer); 82 } 83 84 // RAII Object providing exclusive access to the SPI device. Enables 85 // thread-safe Read()/Write()/WriteRead() operations, as well as composite 86 // operations consisting of multiple, uninterrupted transfers, with 87 // configurable chip-select behavior. 88 class Transaction final { 89 public: 90 Transaction() = delete; ~Transaction()91 ~Transaction() { 92 if ((selector_ != nullptr) && 93 (behavior_ == ChipSelectBehavior::kPerTransaction) && 94 (!first_write_read_)) { 95 selector_->Deactivate() 96 .IgnoreError(); // TODO: b/242598609 - Handle Status properly 97 } 98 } 99 100 // Transaction objects are moveable but not copyable Transaction(Transaction && other)101 Transaction(Transaction&& other) 102 : initiator_(std::move(other.initiator_)), 103 config_(other.config_), 104 selector_(other.selector_), 105 behavior_(other.behavior_), 106 first_write_read_(other.first_write_read_) { 107 other.selector_ = nullptr; 108 } 109 110 Transaction& operator=(Transaction&& other) { 111 initiator_ = std::move(other.initiator_); 112 config_ = other.config_; 113 selector_ = other.selector_; 114 other.selector_ = nullptr; 115 behavior_ = other.behavior_; 116 first_write_read_ = other.first_write_read_; 117 return *this; 118 } 119 120 Transaction(const Transaction&) = delete; 121 Transaction& operator=(const Transaction&) = delete; 122 123 // Synchronously read data from the SPI responder until the provided 124 // `read_buffer` is full. 125 // 126 // Returns OkStatus() on success, and implementation-specific values on 127 // failure. Read(ByteSpan read_buffer)128 Status Read(ByteSpan read_buffer) { return WriteRead({}, read_buffer); } 129 130 // Synchronously write the contents of `write_buffer` to the SPI responder 131 // 132 // Returns OkStatus() on success, and implementation-specific values on 133 // failure. Write(ConstByteSpan write_buffer)134 Status Write(ConstByteSpan write_buffer) { 135 return WriteRead(write_buffer, {}); 136 } 137 138 // Perform a synchronous read/write transfer on the SPI bus. Data from the 139 // `write_buffer` object is written to the bus, while the `read_buffer` is 140 // populated with incoming data on the bus. The operation will ensure that 141 // all requested data is written-to and read-from the bus. In the event the 142 // read buffer is smaller than the write buffer (or zero-size), any 143 // additional input bytes are discarded. In the event the write buffer is 144 // smaller than the read buffer (or zero size), the output is padded with 145 // 0-bits for the remainder of the transfer. 146 // 147 // Returns OkStatus() on success, and implementation-specific values on 148 // failure. WriteRead(ConstByteSpan write_buffer,ByteSpan read_buffer)149 Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer) { 150 // Lazy-init: Configure the SPI bus when performing the first transfer in 151 // a transaction. 152 if (first_write_read_) { 153 PW_TRY(initiator_->Configure(config_)); 154 } 155 156 if ((behavior_ == ChipSelectBehavior::kPerWriteRead) || 157 (first_write_read_)) { 158 PW_TRY(selector_->Activate()); 159 first_write_read_ = false; 160 } 161 162 auto status = initiator_->WriteRead(write_buffer, read_buffer); 163 164 if (behavior_ == ChipSelectBehavior::kPerWriteRead) { 165 PW_TRY(selector_->Deactivate()); 166 } 167 168 return status; 169 } 170 171 private: 172 friend Device; Transaction(sync::BorrowedPointer<Initiator> initiator,const Config & config,ChipSelector & selector,ChipSelectBehavior & behavior)173 explicit Transaction(sync::BorrowedPointer<Initiator> initiator, 174 const Config& config, 175 ChipSelector& selector, 176 ChipSelectBehavior& behavior) 177 : initiator_(std::move(initiator)), 178 config_(config), 179 selector_(&selector), 180 behavior_(behavior), 181 first_write_read_(true) {} 182 183 sync::BorrowedPointer<Initiator> initiator_; 184 Config config_; 185 ChipSelector* selector_; 186 ChipSelectBehavior behavior_; 187 bool first_write_read_; 188 }; 189 190 // Begin a transaction with the SPI device. This creates an RAII 191 // `Transaction` object that ensures that only one entity can access the 192 // underlying SPI bus (Initiator) for the object's duration. The `behavior` 193 // parameter provides a means for a client to select how the chip-select 194 // signal will be applied on Read/Write/WriteRead calls taking place with the 195 // Transaction object. A value of `kPerWriteRead` will activate/deactivate 196 // chip-select on each operation, while `kPerTransaction` will hold the 197 // chip-select active for the duration of the Transaction object. StartTransaction(ChipSelectBehavior behavior)198 Transaction StartTransaction(ChipSelectBehavior behavior) { 199 return Transaction(initiator_.acquire(), config_, selector_, behavior); 200 } 201 202 private: 203 sync::Borrowable<Initiator> initiator_; 204 const Config config_; 205 ChipSelector& selector_; 206 }; 207 208 } // namespace pw::spi 209