7#include "platforms/shared/spi_hw_1.h"
8#include "platforms/shared/spi_hw_2.h"
9#include "platforms/shared/spi_hw_4.h"
10#include "platforms/shared/spi_hw_8.h"
11#include "platforms/shared/spi_transposer.h"
33 size_t num_lanes =
config.data_pins.size();
34 for (
size_t i = 0; i < num_lanes; i++) {
63 size_t num_lanes = config.data_pins.size();
64 if (num_lanes < 1 || num_lanes > 8) {
65 FL_WARN(
"MultiLaneDevice: Invalid number of data pins (" << num_lanes <<
"), must be 1-8");
68 FL_DBG(
"MultiLaneDevice: Created with " << num_lanes <<
" lane(s)");
82 if (
pImpl->initialized) {
87 size_t num_lanes =
pImpl->config.data_pins.size();
90 if (num_lanes < 1 || num_lanes > 8) {
97 const auto& controllers = SpiHw1::getAll();
98 if (controllers.empty()) {
99 FL_WARN(
"MultiLaneDevice: No Single-SPI hardware available");
105 for (
const auto& ctrl : controllers) {
106 if (!ctrl->isInitialized()) {
113 FL_WARN(
"MultiLaneDevice: All Single-SPI controllers in use");
118 SpiHw1::Config hw_config;
119 hw_config.bus_num =
static_cast<u8>(hw->getBusId());
120 hw_config.clock_speed_hz =
pImpl->config.clock_speed_hz;
121 hw_config.clock_pin =
pImpl->config.clock_pin;
122 hw_config.data_pin =
pImpl->config.data_pins[0];
124 if (!hw->begin(hw_config)) {
125 FL_WARN(
"MultiLaneDevice: Failed to initialize Single-SPI hardware");
130 pImpl->backend_type = 1;
131 FL_DBG(
"MultiLaneDevice: Initialized Single-SPI (" << hw->getName() <<
")");
133 }
else if (num_lanes == 2) {
135 const auto& controllers = SpiHw2::getAll();
136 if (controllers.empty()) {
137 FL_WARN(
"MultiLaneDevice: No Dual-SPI hardware available");
143 for (
const auto& ctrl : controllers) {
144 if (!ctrl->isInitialized()) {
151 FL_WARN(
"MultiLaneDevice: All Dual-SPI controllers in use");
156 SpiHw2::Config hw_config;
157 hw_config.bus_num =
static_cast<u8>(hw->getBusId());
158 hw_config.clock_speed_hz =
pImpl->config.clock_speed_hz;
159 hw_config.clock_pin =
pImpl->config.clock_pin;
160 hw_config.data0_pin =
pImpl->config.data_pins[0];
161 hw_config.data1_pin =
pImpl->config.data_pins[1];
163 if (!hw->begin(hw_config)) {
164 FL_WARN(
"MultiLaneDevice: Failed to initialize Dual-SPI hardware");
169 pImpl->backend_type = 2;
170 FL_DBG(
"MultiLaneDevice: Initialized Dual-SPI (" << hw->getName() <<
")");
172 }
else if (num_lanes >= 3 && num_lanes <= 4) {
174 const auto& controllers = SpiHw4::getAll();
175 if (controllers.empty()) {
176 FL_WARN(
"MultiLaneDevice: No Quad-SPI hardware available");
182 for (
const auto& ctrl : controllers) {
183 if (!ctrl->isInitialized()) {
190 FL_WARN(
"MultiLaneDevice: All Quad-SPI controllers in use");
195 SpiHw4::Config hw_config;
196 hw_config.bus_num =
static_cast<u8>(hw->getBusId());
197 hw_config.clock_speed_hz =
pImpl->config.clock_speed_hz;
198 hw_config.clock_pin =
pImpl->config.clock_pin;
199 hw_config.data0_pin =
pImpl->config.data_pins[0];
200 hw_config.data1_pin = (num_lanes > 1) ?
pImpl->config.data_pins[1] : -1;
201 hw_config.data2_pin = (num_lanes > 2) ?
pImpl->config.data_pins[2] : -1;
202 hw_config.data3_pin = (num_lanes > 3) ?
pImpl->config.data_pins[3] : -1;
204 if (!hw->begin(hw_config)) {
205 FL_WARN(
"MultiLaneDevice: Failed to initialize Quad-SPI hardware");
210 pImpl->backend_type = 4;
211 FL_DBG(
"MultiLaneDevice: Initialized Quad-SPI (" << hw->getName() <<
")");
213 }
else if (num_lanes >= 5 && num_lanes <= 8) {
215 const auto& controllers = SpiHw8::getAll();
216 if (controllers.empty()) {
217 FL_WARN(
"MultiLaneDevice: No Octal-SPI hardware available");
223 for (
const auto& ctrl : controllers) {
224 if (!ctrl->isInitialized()) {
231 FL_WARN(
"MultiLaneDevice: All Octal-SPI controllers in use");
236 SpiHw8::Config hw_config;
237 hw_config.bus_num =
static_cast<u8>(hw->getBusId());
238 hw_config.clock_speed_hz =
pImpl->config.clock_speed_hz;
239 hw_config.clock_pin =
pImpl->config.clock_pin;
240 hw_config.data0_pin =
pImpl->config.data_pins[0];
241 hw_config.data1_pin = (num_lanes > 1) ?
pImpl->config.data_pins[1] : -1;
242 hw_config.data2_pin = (num_lanes > 2) ?
pImpl->config.data_pins[2] : -1;
243 hw_config.data3_pin = (num_lanes > 3) ?
pImpl->config.data_pins[3] : -1;
244 hw_config.data4_pin = (num_lanes > 4) ?
pImpl->config.data_pins[4] : -1;
245 hw_config.data5_pin = (num_lanes > 5) ?
pImpl->config.data_pins[5] : -1;
246 hw_config.data6_pin = (num_lanes > 6) ?
pImpl->config.data_pins[6] : -1;
247 hw_config.data7_pin = (num_lanes > 7) ?
pImpl->config.data_pins[7] : -1;
249 if (!hw->begin(hw_config)) {
250 FL_WARN(
"MultiLaneDevice: Failed to initialize Octal-SPI hardware");
255 pImpl->backend_type = 8;
256 FL_DBG(
"MultiLaneDevice: Initialized Octal-SPI (" << hw->getName() <<
")");
259 pImpl->initialized =
true;
272 pImpl->releaseBackend();
279 FL_DBG(
"MultiLaneDevice: Shutdown complete");
287 if (!
pImpl || lane_id >=
pImpl->lanes.size()) {
288 FL_WARN(
"MultiLaneDevice: Invalid lane ID " << lane_id);
290 static Lane dummy_lane(0,
nullptr);
293 return pImpl->lanes[lane_id];
303 "Device not initialized");
307 size_t expected_size = 0;
308 bool found_first =
false;
310 for (
size_t i = 0; i <
pImpl->lanes.size(); i++) {
311 size_t lane_size =
pImpl->lanes[i].bufferSize();
316 expected_size = lane_size;
318 }
else if (lane_size != expected_size) {
320 FL_WARN(
"MultiLaneDevice: Lane size mismatch - expected " << expected_size <<
" bytes (lane 0), but lane " << i <<
" has " << lane_size <<
" bytes");
322 "Lane size mismatch: all lanes must have identical sizes");
327 if (expected_size == 0) {
328 FL_WARN(
"MultiLaneDevice: No data to flush (all lanes empty)");
330 "No data to transmit");
337 DMABuffer dma_buffer =
pImpl->backend->acquireDMABuffer(
max_size);
339 if (!dma_buffer.ok()) {
340 FL_WARN(
"MultiLaneDevice: Failed to acquire DMA buffer");
342 "Failed to acquire DMA buffer");
346 const char* error =
nullptr;
347 bool transpose_ok =
false;
349 if (
pImpl->backend_type == 1) {
351 if (
pImpl->lanes.size() > 0) {
356 if (lane_data.
size() != dma_data.
size()) {
357 FL_WARN(
"MultiLaneDevice: DMA buffer size mismatch - expected " << lane_data.
size() <<
" bytes, got " << dma_data.
size() <<
" bytes");
358 error =
"DMA buffer size mismatch";
359 transpose_ok =
false;
362 for (
size_t i = 0; i < lane_data.
size(); i++) {
363 dma_data[i] = lane_data[i];
368 error =
"No lanes configured";
369 transpose_ok =
false;
372 }
else if (
pImpl->backend_type == 2) {
375 if (
pImpl->lanes.size() > 0) {
377 pImpl->lanes[0].data(),
381 if (
pImpl->lanes.size() > 1) {
383 pImpl->lanes[1].data(),
390 }
else if (
pImpl->backend_type == 4) {
393 for (
size_t i = 0; i <
pImpl->lanes.size() && i < 4; i++) {
395 pImpl->lanes[i].data(),
401 dma_buffer.data(), &error);
403 }
else if (
pImpl->backend_type == 8) {
406 for (
size_t i = 0; i <
pImpl->lanes.size() && i < 8; i++) {
408 pImpl->lanes[i].data(),
417 FL_WARN(
"MultiLaneDevice: Transposition failed - " << ((error ? error :
"unknown error")));
419 error ? error :
"Transposition failed");
423 bool transmit_ok =
pImpl->backend->transmit(TransmitMode::ASYNC);
426 FL_WARN(
"MultiLaneDevice: Hardware transmit failed");
428 "Hardware transmit failed");
436 FL_DBG(
"MultiLaneDevice: Flushed " <<
pImpl->lanes.size() <<
" lanes (" <<
max_size <<
" bytes per lane)");
449 return pImpl->backend->waitComplete(timeout_ms);
458 return pImpl->backend->isBusy();
463 FL_WARN(
"MultiLaneDevice: Not ready for write");
467 if (lane_data.size() >
pImpl->lanes.size()) {
468 FL_WARN(
"MultiLaneDevice: Too many lanes provided (" << lane_data.size() <<
" > " <<
pImpl->lanes.size() <<
")");
473 if (lane_data.size() > 1) {
474 size_t first_size = lane_data[0].size();
475 for (
size_t i = 1; i < lane_data.size(); i++) {
476 if (lane_data[i].size() != first_size) {
477 FL_WARN(
"MultiLaneDevice: Lane size mismatch - lane 0 has " << first_size <<
" bytes, lane " << i <<
" has " << lane_data[i].size() <<
" bytes");
478 return WriteResult(
"Lane size mismatch: all lanes must have identical sizes");
487 for (
size_t i = 0; i < lane_data.size(); i++) {
488 pImpl->lanes[i].write(lane_data[i].data(), lane_data[i].size());
492 auto flush_result =
flush();
493 if (!flush_result.ok()) {
494 FL_WARN(
"MultiLaneDevice: Flush failed");
498 FL_DBG(
"MultiLaneDevice: Wrote " << lane_data.size() <<
" lanes atomically (async)");
505 static const Config empty_config;
static bool transpose8(const fl::optional< LaneData > lanes[8], fl::span< u8 > output, const char **error=nullptr) FL_NO_EXCEPT
Transpose 8 lanes of data into interleaved octal-SPI format.
static bool transpose4(const fl::optional< LaneData > &lane0, const fl::optional< LaneData > &lane1, const fl::optional< LaneData > &lane2, const fl::optional< LaneData > &lane3, fl::span< u8 > output, const char **error=nullptr) FL_NO_EXCEPT
Transpose 4 lanes of data into interleaved quad-SPI format.
static bool transpose2(const fl::optional< LaneData > &lane0, const fl::optional< LaneData > &lane1, fl::span< u8 > output, const char **error=nullptr) FL_NO_EXCEPT
Transpose 2 lanes of data into interleaved dual-SPI format.
Lane data structure: payload + padding frame.
static expected failure(E err, const char *msg=nullptr) FL_NO_EXCEPT
Create error result.
static expected success(T value) FL_NO_EXCEPT
Create successful result.
const T * data() const FL_NO_EXCEPT
constexpr fl::size size() const FL_NO_EXCEPT
Single lane in a multi-lane SPI device.
bool waitComplete(u32 timeout_ms=(fl::numeric_limits< u32 >::max)()) FL_NO_EXCEPT
Wait for pending transmission to complete.
MultiLaneDevice(const Config &config) FL_NO_EXCEPT
Construct multi-lane device.
const Config & getConfig() const FL_NO_EXCEPT
Get current configuration.
Lane & lane(size_t lane_id) FL_NO_EXCEPT
Get access to a specific lane.
Result< void > flush() FL_NO_EXCEPT
Flush all lanes (transpose and transmit)
bool isBusy() const FL_NO_EXCEPT
Check if transmission is in progress.
fl::unique_ptr< Impl > pImpl
void end() FL_NO_EXCEPT
Shutdown hardware and release resources.
size_t numLanes() const FL_NO_EXCEPT
Get number of lanes.
WriteResult writeImpl(fl::span< const fl::span< const u8 > > lane_data) FL_NO_EXCEPT
Internal implementation - write all lanes atomically.
fl::optional< fl::task::Error > begin() FL_NO_EXCEPT
Initialize hardware.
~MultiLaneDevice() FL_NO_EXCEPT
Destructor - releases hardware resources.
bool isReady() const FL_NO_EXCEPT
Check if device is initialized.
Shared implementation helpers for SPI device classes.
Centralized logging categories for FastLED hardware interfaces and subsystems.
Multi-lane SPI device for 2-8 independent LED strips.
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NO_EXCEPT
fl::result< T, SPIError > Result
u8 u8 fract8 blur_amount FL_NO_EXCEPT
bool flush(u32 timeoutMs) FL_NO_EXCEPT
constexpr nullopt_t nullopt
Base definition for an LED controller.
Result of a write operation.
void clearBackend() FL_NO_EXCEPT
Clear backend state.
bool initialized
Whether hardware is initialized.
DeviceImplBase() FL_NO_EXCEPT
Constructor.
fl::shared_ptr< SpiHwBase > backend
Polymorphic SPI hardware backend (SpiHw1/2/4/8)
Configuration for multi-lane SPI.
void releaseBackend() FL_NO_EXCEPT
Impl(const Config &cfg) FL_NO_EXCEPT