xref: /aosp_15_r20/external/pigweed/pw_spi/public/pw_spi/device.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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