GCC Code Coverage Report


Directory: ./
File: non-gpl/DPIPixels/DPIPixels.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 425 552 77.0%
Functions: 20 21 95.2%
Branches: 454 961 47.2%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the CC-BY-ND as described in the
10 * included LICENSE.CC-BY-ND file. This file may be modified for
11 * personal use, but modified copies MAY NOT be redistributed in any form.
12 */
13
14 #include "fpp-pch.h"
15
16 #include "fpp-json.h"
17
18 #include <algorithm>
19 #include <cmath>
20 #include <vector>
21
22 #include <sys/ioctl.h>
23 #include <sys/mman.h>
24 #include <ctime>
25 #include <fcntl.h>
26 #include <unistd.h>
27
28 #include "../../Sequence.h"
29 #include "../../Warnings.h"
30 #include "../../common.h"
31 #include "../../log.h"
32 #include "../../settings.h"
33
34 #include "DPIPixels.h"
35 #include "../CapeUtils/CapeUtils.h"
36 #include "channeloutput/channeloutputthread.h"
37 #include "channeloutput/stringtesters/PixelStringTester.h"
38 #include "util/GPIOUtils.h"
39
40 #include "../../overlays/PixelOverlay.h"
41
42 #define _0H 1
43 #define _0L 2
44 #define _1H 2
45 #define _1L 1
46 #define _H(b) ((b) ? _1H : _0H)
47 #define _L(b) ((b) ? _1L : _0L)
48
49 #define POSITION_TO_BITMASK(x) (0x000001 << (x))
50
51 // Blank scanlines at the top of every frame, before the first WS bit. The
52 // very first FB pixel of a frame is the first third of channel 0's MSB, i.e.
53 // the first pixel's red MSB, and it is emitted at the moment the scan-out
54 // pipeline restarts after vertical blanking (display list reload, DMA/FIFO
55 // priming). Any stretch of that first high period there reads as a 1 on
56 // pixels with a tight T0H threshold: a red first pixel. Starting the frame
57 // with fully low lines moves the pipeline start-up into the reset gap where
58 // it cannot matter. Costs ~50us of low per row, which only lengthens reset.
59 constexpr int DPI_LEAD_ROWS = 2;
60
61 // FB pixels the non-latched WS bit stays high before its data section, i.e.
62 // the 0-bit high time in 26ns steps. The bit cell is 48 px: this many high,
63 // then 16 px of data (high for a 1) written by PrepData(), then low. 12 gives
64 // T0H 312ns / T1H 729ns, which is what FPP's PRU string drivers have shipped
65 // for years (320/750) and sits inside every datasheet window: WS2811 wants a
66 // 0-bit high of 100-400ns, SK6812/WS2815 150-450, WS2812B 250-550. The old
67 // even thirds gave 417ns, outside the WS2811 window, and the first pixel on a
68 // string of older WS2811 parts (the only one that sees the Pi's raw edge,
69 // every later pixel gets a regenerated one) would randomly read 0 bits as 1.
70 // The latched layout keeps its 16/16/16 thirds: its 4-latch pulse train needs
71 // the 16 px phases.
72 constexpr int DPI_T0H_PX = 12;
73
74 // Latched outputs get the same 312/729ns cell when the cape uses at most
75 // three latch banks. A phase is one 4 px slot per bank (set data, LE high for
76 // 2 px, hold), so with L banks the "go high" slots occupy px 0..4L-1 and the
77 // "go to data" slots can start at px 12 and the "go low" slots at px 28 for
78 // L <= 3 without overlapping. Each bank's LE fires at the same offset within
79 // its phase, so every bank sees T0H = 12 px and T1H = 28 px exactly. Four
80 // banks need the full 16 px phases (16/16/16, T0H 417ns) because the fourth
81 // "go high" slot sits at px 12..15. The LE pulse, setup and hold are the same
82 // in both layouts.
83
1/2
✓ Branch 0 taken 4807 times.
✗ Branch 1 not taken.
4807 inline int latchDataPhasePx(int latchCount) { return latchCount <= 3 ? 12 : 16; }
84
1/2
✓ Branch 0 taken 4800 times.
✗ Branch 1 not taken.
4800 inline int latchLowPhasePx(int latchCount) { return latchCount <= 3 ? 28 : 32; }
85
86 // Uncomment to log elapsed time in PrepData()
87 // #define LOG_ELAPSED_TIME
88 // Uncomment to enable the HSync (P1-5) and VSync (P1-3) pins for logic analyzing
89 // WARNING: THIS WILL BREAK I2C ON THE DEVICE SINCE THE HSYNC/VSYNC PINS ARE SHARED WITH I2C
90 // #define LOGIC_ANALYZING
91
92 /*
93 * New DPI config as of 2026. Rather than allowing a max of 800 or 1600
94 * pixels per string and having banks share the max of 800 or 1600, the
95 * DPI clock is now set to a much higher rate allowing the banks to be
96 * interleaved allowing up to 80 outputs using 4 latch pins. For boards
97 * supporting the standard 16-output headers, this would allow up to 64
98 * string outputs each supporting 800 or 1600 pixels at full frame rate.
99 *
100 * To use this code, the framebuffer must be setup to a specific resolution
101 * and timing. This is normally handled by setupChannelOutputs in
102 * /opt/fpp/src/boot/FPPINIT.cpp.
103 *
104 * This requires a dtoverlay to set the appropriate clock frequency and
105 * resolution and other timing parameters. The source for the FPP overlay
106 * is in /opt/fpp/capes/drivers/pi. The overlay only needs to establish the
107 * 1920-wide, 38.4MHz DPI pipeline; its vertical resolution is just a boot
108 * default and is overridden at runtime (see below).
109 *
110 * The width is 1920 and the pixel clock 38.4MHz, which makes each WS bit 48 FB
111 * pixels (1.25us). The vertical resolution is sized at Init() to the longest
112 * configured string: each scanline carries 5 channels, so the data occupies
113 * ceil(longestString/5) scanlines plus a small blank reset tail. The output
114 * frame rate is then set per-sequence to min(sequenceRate, maxRateForLength) by
115 * varying ONLY the vertical blanking (vtotal) via FrameBuffer::SetRefreshRate();
116 * on the Pi5/RP1 DPI this is a runtime modeset that needs no reboot. The
117 * clock stays fixed so the WS bit timing and framebuffer layout never change.
118 * fps * maxChannels is bounded by the WS281x 800kHz bit rate (~96000), e.g. at
119 * 38.4MHz: 40fps<->2400ch, 60fps<->1600ch, 80fps<->1200ch, 100fps<->960ch.
120 *
121 * 48 FrameBuffer pixels are used to drive each WS bit for all channels. This
122 * allows 16 FB pixels to set the initial On state, 16 FB pixels that are On/Off
123 * to set the WS bit on or off, and the trailing 16 FB pixels are off
124 * for the trailing third of the WS bit timing. When using latches, the
125 * 16 FB pixels allow turning up to 4 latches on/off while still
126 * honoring the WS protocol timing. The different groups of pixels on
127 * each latched output are offset by only 104 microseconds from each
128 * other rather than being in banks as they previously were meaning
129 * bank 3 would be sent after all the pixels for banks 0-2 were sent.
130 *
131 * The latching process is as follows (time offset in nanoseconds(ns)):
132 *
133 * 000ns - FB pixel 0 sets the data bits for latch #0's pixels
134 * 026ns - FB pixel 1 holds the data bits and turns latch #0 pin on
135 * 052ns - FB pixel 2 holds the data bits and turns latch #0 pin off
136 * 078ns - FB pixel 3 holds the data bits
137 * 104ns - FB pixel 4 sets the data bits for latch #1's pixels
138 * 130ns - FB pixel 5 holds the data bits and turns latch #1 pin on
139 * 156ns - FB pixel 6 holds the data bits and turns latch #1 pin off
140 * 182ns - FB pixel 7 holds the data bits
141 * 208ns - FB pixel 8 sets the data bits for latch #2's pixels
142 * ...
143 * 416ns - FB pixel 16 sets the data bits for latch #0's pixels (2nd WS bit)
144 * ...
145 *
146 * This sequence allows the data bits to be set 26ns before the
147 * latch-enable bits are turned on and the data bits are not changed
148 * until at least 26ns after the latch-enable bits are turned off.
149 * Newer latch chips do not need anywhere near this time and the timing
150 * still works with older chips still in use.
151 *
152 * This same spacing of 48 FB pixels per WS bit is still used when
153 * latch chips are not in use, but since there is no latching, the
154 * first 16 FB pixels are identical to each other, the second 16 are
155 * identical to each other, and the last 16 FB pixels are identical
156 * to each other.
157 *
158 */
159
160 #include "Plugin.h"
161 class DPIPixelsOutputPlugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin {
162 public:
163 14 DPIPixelsOutputPlugin() :
164
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
42 FPPPlugins::Plugin("DPIPixels") {
165 14 }
166 14 virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override {
167
2/6
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
28 return new DPIPixelsOutput(startChannel, channelCount);
168 }
169 };
170
171 extern "C" {
172 14 FPPPlugins::Plugin* createPlugin() {
173
2/6
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
28 return new DPIPixelsOutputPlugin();
174 }
175 }
176
177 /////////////////////////////////////////////////////////////////////////////
178
179 14 DPIPixelsOutput::DPIPixelsOutput(unsigned int startChannel, unsigned int channelCount) :
180
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
70 ChannelOutput(startChannel, channelCount) {
181
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 LogDebug(VB_CHANNELOUT, "DPIPixelsOutput::DPIPixelsOutput(%u, %u)\n",
182 startChannel, channelCount);
183 14 }
184
185 56 DPIPixelsOutput::~DPIPixelsOutput() {
186 28 LogDebug(VB_CHANNELOUT, "DPIPixelsOutput::~DPIPixelsOutput()\n");
187 // Idempotent; Close() normally did this, but the listener captures this.
188 84 unregisterSettingsListener("DPIPixels", "E131BridgingInterval");
189
190
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
28 if (!m_configuredDPIPins.empty()) {
191
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
4 for (const auto& pinName : m_configuredDPIPins) {
192 try {
193
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
2 const PinCapabilities& pin = PinCapabilities::getPinByName(pinName);
194
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
2 pin.releasePin();
195 ✗ } catch (...) {
196 ✗ }
197 }
198 }
199
200
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 14 times.
80 for (auto a : pixelStrings) {
201
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
52 delete a;
202 }
203 28 pixelStrings.clear();
204
205
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
28 if (onOffMap)
206 28 free(onOffMap);
207
208
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 1 times.
28 if (m_shadow)
209 26 free(m_shadow);
210
211
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 1 times.
28 if (fb)
212
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
26 delete fb;
213 56 }
214
215 // The RPIWS281X driver has been removed and its outputs are now driven by DPI. Most
216 // RPIWS281X capes ship a DPIPixels variant of their string config in the same EEPROM
217 // with an identical pin list, so prefer that. rPi-28D is special cased: its RPIWS281X
218 // config's third output is ws2801 over SPI which DPI cannot drive, so it maps to the
219 // 4-output variant that repurposes CN1's clock/data pins as WS281x outputs.
220 static std::map<std::string, std::string> RPIWS281X_SUBTYPE_MAP = {
221 { "PiHat", "PiHat-DPIPixels" },
222 { "rPi-MFC", "rPi-MFC-DPIPixels" },
223 { "rPi-28D", "rPi-28D-DPIPixels-4" }
224 };
225
226 14 bool DPIPixelsOutput::LoadStringConfig(const std::string& subType, Json::Value& root, std::string& usedSubType) {
227
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 auto it = RPIWS281X_SUBTYPE_MAP.find(subType);
228
6/8
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 13 times.
14 if ((it != RPIWS281X_SUBTYPE_MAP.end()) && CapeUtils::INSTANCE.getStringConfig(it->second, root)) {
229
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 usedSubType = it->second;
230 1 return true;
231 }
232 // A cape we don't know about. If it ships a "-DPIPixels" variant, use it.
233
10/26
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 13 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 13 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 13 times.
✓ Branch 12 taken 13 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 13 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 13 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✓ Branch 19 taken 13 times.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
39 if (!endsWith(subType, "-DPIPixels") && CapeUtils::INSTANCE.getStringConfig(subType + "-DPIPixels", root)) {
234 ✗ usedSubType = subType + "-DPIPixels";
235 ✗ return true;
236 }
237 // Fall back to the config as named. For a third-party cape with a physically burned
238 // EEPROM this may be an RPIWS281X config, which uses the same pin format we do.
239
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 if (CapeUtils::INSTANCE.getStringConfig(subType, root)) {
240
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 usedSubType = subType;
241 13 return true;
242 }
243 ✗ return false;
244 }
245
246 14 int DPIPixelsOutput::Init(Json::Value config) {
247
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 LogDebug(VB_CHANNELOUT, "DPIPixelsOutput::Init(JSON)\n");
248
249
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 std::string subType = config["subType"].asString();
250
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
14 if (subType == "") {
251 ✗ subType = "DPIPixels";
252 }
253
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 Json::Value root;
254 char filename[256];
255
256
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 CapeUtils::INSTANCE.initCape(true);
257
258
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 licensedOutputs = CapeUtils::INSTANCE.getLicensedOutputs();
259
260 14 std::string usedSubType;
261
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
14 if (!LoadStringConfig(subType, root, usedSubType)) {
262 ✗ LogErr(VB_CHANNELOUT, "Could not read pin configuration for %s\n", subType.c_str());
263 ✗ WarningHolder::AddWarning(13, "DPIPixels: Could not read pin configuration for " + subType);
264 ✗ return 0;
265 }
266
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
14 if (usedSubType != subType) {
267
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogInfo(VB_CHANNELOUT, "Mapped string config %s to %s for DPI output\n",
268 subType.c_str(), usedSubType.c_str());
269 }
270
271
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 2 times.
14 if (licensedOutputs == 0) {
272 // Two strings driven directly off the Pi's GPIO header is what the old RPIWS281X
273 // driver provided for free, so DPI has to keep providing it. More than two
274 // outputs still requires a license.
275 12 licensedOutputs = 2;
276 }
277
278
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 config["base"] = root;
279
280 // A cape with a physical EEPROM has every pin hard-wired to an output buffer, so all of
281 // its pins are muxed for DPI even when a port is left empty, otherwise the buffer input
282 // would float. A virtual EEPROM describes a hand-wired setup where the unused ports may
283 // not be connected to anything, so only the pins for ports with channels configured are
284 // claimed, leaving the rest available for other uses such as GPIO inputs.
285
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 const Json::Value& capeInfo = CapeUtils::INSTANCE.getCapeInfo();
286
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 bool virtualEEPROM = capeInfo["eepromLocation"].asString().find("sys/bus/i2c") == std::string::npos;
287
288
2/2
✓ Branch 0 taken 1344 times.
✓ Branch 1 taken 14 times.
1358 for (int i = 0; i < (MAX_DPI_PIXEL_LATCHES * 24); i++) {
289 1344 bitPos[i] = -1; // unused pin, no output assigned
290 1344 outputToStringMap[i] = -1;
291 1344 stringToOutputMap[i] = -1;
292 }
293
294 14 std::vector<std::string> outputPinMap;
295 14 std::vector<int> outputChannelCounts;
296 14 std::string outputList;
297 14 std::string skippedOutputs;
298 int firstStringInBank[MAX_DPI_PIXEL_LATCHES];
299
300 14 memset(latchPinMasks, 0, sizeof(latchPinMasks));
301 14 memset(stringLengths, 0, sizeof(stringLengths));
302 14 memset(firstStringInBank, 0, sizeof(firstStringInBank));
303
304
4/6
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 28 times.
✓ Branch 5 taken 14 times.
42 for (int i = 0; i < config["outputs"].size(); i++) {
305
3/6
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 28 times.
28 if (i >= root["outputs"].size()) {
306 1 continue;
307 }
308 // bitPos and the string maps hold one entry per DPI bit and latch
309
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
28 if (i >= MAX_DPI_PIXEL_LATCHES * 24) {
310 ✗ LogErr(VB_CHANNELOUT, "DPIPixels: only %d outputs are supported, ignoring the rest\n",
311 MAX_DPI_PIXEL_LATCHES * 24);
312 ✗ break;
313 }
314
3/6
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
28 Json::Value s = config["outputs"][i];
315
3/8
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 28 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
28 PixelString* newString = new PixelString(true);
316
317
5/10
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 28 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 28 times.
28 if (!newString->Init(s, &root["outputs"][i]))
318 ✗ return 0;
319
320 // The license limit is applied first: the lengths recorded below
321 // (longestString, stringLengths) size and bound what PrepData reads,
322 // and taken before the limit they kept the unlicensed length.
323
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
28 if ((i >= licensedOutputs) && (newString->m_outputBytes > 0)) {
324 // apply limit at the source, same code as BBB48StringOutput
325 ✗ int pixels = 50;
326 ✗ int chanCount = 0;
327 ✗ for (auto& a : newString->m_virtualStrings) {
328 ✗ if (pixels < a.pixelCount) {
329 ✗ a.pixelCount = pixels;
330 ✗ if (outputList != "") {
331 ✗ outputList += ", ";
332 }
333 ✗ outputList += std::to_string(i + 1);
334 }
335 ✗ pixels -= a.pixelCount;
336 ✗ chanCount += a.pixelCount * a.channelsPerNode();
337 }
338 ✗ if (newString->m_isSmartReceiver) {
339 ✗ chanCount = 0;
340 ✗ if (outputList != "") {
341 ✗ outputList += ", ";
342 }
343 ✗ outputList += std::to_string(i + 1);
344 }
345 ✗ newString->setPixelDataChannels(chanCount);
346 }
347
348
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 14 times.
28 if (newString->m_outputBytes > longestString)
349 14 longestString = newString->m_outputBytes;
350
351
2/4
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 28 times.
28 if (protocol == "")
352 ✗ protocol = s["protocol"].asString();
353
354
1/2
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
28 outputChannelCounts.push_back(newString->m_outputBytes);
355
356
5/8
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 26 times.
✓ Branch 7 taken 2 times.
28 if (root["outputs"][i].isMember("pin")) {
357
4/8
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 26 times.
✗ Branch 7 not taken.
26 std::string pinName = root["outputs"][i]["pin"].asString();
358
6/14
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 26 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 26 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 26 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
26 int pinBitPos = startsWith(pinName, "P") ? GetDPIPinBitPosition(pinName) : -1;
359
360
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if (pinBitPos >= 0) {
361 26 bitPos[i] = pinBitPos;
362 26 outputToStringMap[pinBitPos] = i;
363 26 stringToOutputMap[i] = pinBitPos;
364 26 stringLengths[pinBitPos] = newString->m_outputBytes;
365
366
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 outputPinMap.push_back(pinName);
367 } else {
368 // A pin DPI cannot drive. The old RPIWS281X configs put ws2801 strings on
369 // spidev pins, so skip just that output rather than failing the whole cape.
370 ✗ if (newString->m_outputBytes > 0) {
371 ✗ if (skippedOutputs != "") {
372 ✗ skippedOutputs += ", ";
373 }
374 ✗ skippedOutputs += std::to_string(i + 1) + " (" + pinName + ")";
375 }
376 ✗ outputPinMap.push_back("");
377 }
378
4/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
28 } else if (root["outputs"][i].isMember("sharedOutput")) {
379
4/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
2 int shared = root["outputs"][i]["sharedOutput"].asInt();
380
4/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
2 std::string pinName = (shared >= 0 && shared < (int)outputPinMap.size()) ? outputPinMap[shared] : std::string();
381
6/10
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
2 int sharedBitPos = pinName.empty() ? -1 : GetDPIPinBitPosition(pinName);
382
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (sharedBitPos < 0) {
383 // an index outside the outputs before it, or one with no DPI pin
384
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_CHANNELOUT, "DPIPixels: output %d shares output %d, which has no DPI pin\n", i + 1, shared + 1);
385
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 outputPinMap.push_back("");
386
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 pixelStrings.push_back(newString);
387 1 continue;
388 }
389 1 bitPos[i] = sharedBitPos;
390
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
4 for (int latch = 1; latch < MAX_DPI_PIXEL_LATCHES; latch++) {
391
3/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
6 if ((firstStringInBank[latch] == 0) &&
392
3/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
3 (std::count(outputPinMap.begin(), outputPinMap.end(), pinName) == latch)) {
393 1 firstStringInBank[latch] = i;
394 1 latchCount = latch + 1;
395 }
396 }
397
398
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
4 for (int z = MAX_DPI_PIXEL_LATCHES - 1; z > 0; z--) {
399
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (firstStringInBank[z] != 0) {
400 1 outputToStringMap[bitPos[i] + (z * 24)] = i;
401 1 stringToOutputMap[i] = bitPos[i] + (z * 24);
402 1 stringLengths[bitPos[i] + (z * 24)] = newString->m_outputBytes;
403 1 z = 0;
404 }
405 }
406
407
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 outputPinMap.push_back(pinName);
408
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 } else {
409 ✗ outputPinMap.push_back("");
410 }
411
412
1/2
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
27 pixelStrings.push_back(newString);
413
2/3
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
28 }
414
415
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
14 if (skippedOutputs != "") {
416 ✗ std::string warning = "DPIPixels: output(s) " + skippedOutputs +
417 ✗ " cannot be driven by DPI and have been disabled. Only pixels on the Pi's DPI pins are supported.";
418 ✗ WarningHolder::AddWarning(warning);
419 ✗ LogWarn(VB_CHANNELOUT, "WARNING: %s\n", warning.c_str());
420 ✗ }
421
422
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 14 times.
42 for (int i = 0; i < outputPinMap.size(); i++) {
423 28 const std::string& pinName = outputPinMap[i];
424
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 27 times.
28 if (pinName.empty())
425 1 continue;
426
6/6
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 26 times.
27 if (virtualEEPROM && (outputChannelCounts[i] == 0))
427 1 continue;
428 // Outputs sharing a pin via latches can each map back to the same pin
429
3/4
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 25 times.
26 if (std::find(m_configuredDPIPins.begin(), m_configuredDPIPins.end(), pinName) != m_configuredDPIPins.end())
430 1 continue;
431
432
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 LogExcess(VB_CHANNELOUT, " Will enable Pin %s for DPI output\n", pinName.c_str());
433
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 m_configuredDPIPins.push_back(pinName);
434 }
435
436
6/8
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 13 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 13 times.
14 if (licensedOutputs && root.isMember("latches")) {
437 1 usingLatches = true;
438
439
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
1 if (root["latches"].size() > 4) {
440 ✗ LogErr(VB_CHANNELOUT, "Error, DPIPixels only supports 4 latch pins, %d configured by cape.\n",
441 root["latches"].size());
442 ✗ WarningHolder::AddWarning("DPIPixels: Max of 4 latch pins supported, but " +
443 ✗ std::to_string(root["latches"].size()) + "configured by cape.");
444 ✗ return 0;
445 }
446
447
4/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
3 for (int l = 0; l < root["latches"].size(); l++) {
448
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 std::string pinName = root["latches"][l].asString();
449
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (pinName[0] == 'P') {
450
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 LogExcess(VB_CHANNELOUT, "Will enable Pin %s for latch\n", pinName.c_str());
451
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 m_configuredDPIPins.push_back(pinName);
452
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 latchPinMasks[l] = POSITION_TO_BITMASK(GetDPIPinBitPosition(pinName));
453 }
454 2 }
455 } else {
456 // FIXME, shouldn't need this line, remove after testing without latch chips
457 // latchCount = 1;
458 }
459
460
5/6
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 14 times.
16 while (!pixelStrings.empty() && pixelStrings.back()->m_outputBytes == 0) {
461 2 PixelString* ps = pixelStrings.back();
462
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 delete ps;
463 2 pixelStrings.pop_back();
464 }
465
466 14 int nonZeroStrings = 0;
467
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 14 times.
40 for (int s = 0; s < pixelStrings.size(); s++) {
468 26 int pixelCount = 0;
469
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 26 times.
52 for (auto& a : pixelStrings[s]->m_virtualStrings) {
470
2/4
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 if ((a.receiverNum == -1) && (a.pixelCount > 0))
471 26 pixelCount += a.pixelCount;
472 }
473
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if (pixelCount) {
474 26 nonZeroStrings++;
475 }
476 }
477
478
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 LogDebug(VB_CHANNELOUT, " Found %d strings (out of %d total) that have 1 or more pixels configured\n",
479 nonZeroStrings, pixelStrings.size());
480
481
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (pixelStrings.size() > licensedOutputs) {
482 ✗ int pixels = 0;
483 ✗ for (int s = 0; s < pixelStrings.size(); s++) {
484 ✗ pixels = pixelStrings[s]->m_outputBytes / 3;
485 ✗ if ((s >= licensedOutputs) && (pixels > 50)) {
486 ✗ if (outputList != "") {
487 ✗ outputList += ", ";
488 }
489
490 ✗ outputList += std::to_string(s + 1);
491 }
492 }
493
494 ✗ if (outputList != "") {
495 ✗ std::string warning = "DPIPixels is licensed for ";
496 ✗ warning += std::to_string(licensedOutputs);
497 ✗ warning += " outputs, but one or more outputs is configured for more than 50 pixels. Output(s): ";
498 ✗ warning += outputList;
499 ✗ WarningHolder::AddWarning(warning);
500 ✗ LogWarn(VB_CHANNELOUT, "WARNING: %s\n", warning.c_str());
501 ✗ }
502 }
503
504
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 if (licensedOutputs) {
505
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 14 times.
70 for (int lp = 0; lp < MAX_DPI_PIXEL_LATCHES; lp++) {
506
2/2
✓ Branch 0 taken 268800 times.
✓ Branch 1 taken 56 times.
268856 for (int p = 0; p < 4800; p++) {
507 268800 onOffMask[lp][p] = 0xFFFFFF;
508 }
509 }
510
511 14 onOffMap = (uint8_t*)malloc(longestString * pixelStrings.size());
512 14 memset(onOffMap, 0xFF, longestString * pixelStrings.size());
513
514
5/6
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
✓ Branch 5 taken 14 times.
40 for (int s = 0; s < pixelStrings.size() && s < licensedOutputs; s++) {
515 26 int start = -1;
516
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 26 times.
52 for (auto& a : pixelStrings[s]->m_gpioCommands) {
517
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if (a.type == 0) {
518 26 start = a.channelOffset;
519 ✗ } else if ((a.type == 1) && (start != -1) && (stringToOutputMap[s] >= 0)) {
520 // a string on no DPI pin (-1) has no bit to mask
521 ✗ usingSmartReceivers = true;
522
523 ✗ int lp = stringToOutputMap[s] / 24;
524
525 // onOffMask holds 4800 channels; a longer string is
526 // refused further on, but this runs first
527 ✗ while (start < a.channelOffset && start < 4800) {
528 // Turn Off bits in the mask as needed
529 ✗ onOffMask[lp][start] &= ~(POSITION_TO_BITMASK(stringToOutputMap[s] % 24));
530 ✗ onOffMap[start * pixelStrings.size() + s] = 0x00; // convert channel offset to pixel offset, store by row
531 ✗ ++start;
532 }
533 }
534 }
535 }
536 }
537
538 // Size the framebuffer height to the longest string. At the fixed DPI
539 // geometry (1920 wide, 48 FB pixels per WS bit) each scanline carries 5
540 // channels, so the WS bit stream for the longest string occupies
541 // ceil(longestString / 5) scanlines. A few trailing blank scanlines
542 // guarantee the >50us WS reset low between frames. The frame rate is then
543 // set at runtime purely by the vertical blanking (see PrepData ->
544 // FrameBuffer::SetRefreshRate), so this height is fixed for the life of the
545 // output and never needs a reboot to change fps.
546 14 constexpr int DPI_CHANNELS_PER_ROW = 5; // 1920 / (3 * 16 fbPixelMult) / 8 bits
547 14 constexpr int DPI_RESET_ROWS = 8; // ~400us guaranteed-low reset tail
548 14 constexpr int DPI_MAX_CHANNELS = 4800; // onOffMask[][4800] limit (1600 pixels)
549 14 int cappedChannels = std::min(longestString, DPI_MAX_CHANNELS);
550 14 int dataRows = (cappedChannels + DPI_CHANNELS_PER_ROW - 1) / DPI_CHANNELS_PER_ROW;
551 14 int neededRows = DPI_LEAD_ROWS + dataRows + DPI_RESET_ROWS;
552
553
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 Json::Value fbConfig;
554 // Width 0 => use the connector's (1920) width. Height is our computed row
555 // count; VariableRefresh tells KMS to drive its own vertical timing so we can
556 // change the refresh rate at runtime without a reboot.
557
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 fbConfig["Width"] = 0;
558
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 fbConfig["Height"] = neededRows;
559
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 fbConfig["VariableRefresh"] = true;
560
561 // Default is now to use KMS which would be named DPI-1
562
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 device = "DPI-1";
563 14 int card = -1;
564
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 1 times.
24 for (int c = 0; c < 10; c++) {
565
5/8
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 23 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 13 times.
✓ Branch 7 taken 10 times.
23 if (FileExists("/sys/class/drm/card" + std::to_string(c) + "-DPI-1/modes")) {
566 13 card = c;
567 13 break;
568 }
569 }
570
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
14 if (card == -1) {
571 // fallbacks, may or may not work
572
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 device = "fb1";
573
5/10
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
1 if (!FileExists("/dev/fb1") && FileExists("/dev/fb0")) {
574
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 device = "fb0";
575 }
576 }
577
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 fbConfig["Device"] = device;
578
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 fbConfig["BitsPerPixel"] = 24;
579 // fbConfig["Pages"] = 3;
580
581 // FIXME - Auto sync is currently consuming 100% of a core, need to figure this out.
582 // #define USE_AUTO_SYNC
583 #ifdef USE_AUTO_SYNC
584 fbConfig["AutoSync"] = true;
585 #endif
586
587
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 fb = FrameBuffer::createFrameBuffer(fbConfig);
588
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
14 if (fb == nullptr) {
589
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_CHANNELOUT, "Error: cannot open FrameBuffer device for %s.\n", device.c_str());
590
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 WarningHolder::AddWarning(13, "DPIPixels: Could not open FrameBuffer device for " + device);
591 1 return 0;
592 }
593
594
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
13 if (!FrameBufferIsConfigured()) {
595 ✗ LogErr(VB_CHANNELOUT, "Error: FrameBuffer %s is not configured for DPI Pixels.\n", device.c_str());
596 ✗ WarningHolder::AddWarning(13, "DPIPixels: FrameBuffer " + device + " is not configured for DPI Pixels");
597 ✗ return 0;
598 }
599
600
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 LogDebug(VB_CHANNELOUT, "The framebuffer device %s was opened successfully.\n", device.c_str());
601
602 13 m_shadow = (uint8_t*)calloc(1, fb->PageSize());
603
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (!m_shadow) {
604 ✗ LogErr(VB_CHANNELOUT, "DPIPixels: could not allocate %d bytes for the shadow page\n", fb->PageSize());
605 ✗ WarningHolder::AddWarning(13, "DPIPixels: could not allocate the shadow page");
606 ✗ return 0;
607 }
608 13 m_shadowCopyBytes = std::min(fb->PageSize(), (DPI_LEAD_ROWS + dataRows) * fb->RowStride());
609
610 // Highest refresh rate this string length allows. KMS starts the display at
611 // that rate (minimal blanking); the actual rate is lowered per-sequence to
612 // min(sequenceRate, m_configuredMaxFps) in PrepData().
613
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 m_configuredMaxFps = fb->GetMaxRefreshRate();
614 13 m_currentFps = m_configuredMaxFps;
615
8/14
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 12 times.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
13 LogInfo(VB_CHANNELOUT, "DPIPixels: framebuffer %dx%d, max refresh %d fps for %d channel longest string, %s\n",
616 fb->Width(), fb->Height(), m_configuredMaxFps, longestString,
617 usingLatches ? (std::to_string(latchCount) + " latch banks").c_str() : "no latches");
618
619 // Don't leave the display at that maximum. PrepData() only retimes the
620 // output once something drives it, and a remote (or otherwise idle) box
621 // may sit for hours with nothing doing so, rescanning the string at
622 // hundreds of Hz from boot. Start at the idle rate PrepData() would pick.
623 // The idle rate follows the E1.31 bridging interval; cache it behind a
624 // settings listener rather than reading the settings map every frame.
625
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 m_idleFps = IdleFpsFromSetting();
626
3/6
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
39 registerSettingsListener("DPIPixels", "E131BridgingInterval", [this](const std::string&) {
627 1 m_idleFps = IdleFpsFromSetting();
628 1 });
629
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (m_configuredMaxFps > 0) {
630
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 int startFps = std::clamp(m_idleFps, 1, m_configuredMaxFps);
631
7/8
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 11 times.
✓ Branch 7 taken 2 times.
13 if (startFps != m_currentFps && fb->SetRefreshRate(startFps)) {
632
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 LogInfo(VB_CHANNELOUT, "DPIPixels: DPI refresh -> %d fps (startup, max %d)\n", startFps, m_configuredMaxFps);
633 11 m_currentFps = startFps;
634 }
635 }
636
637 13 bool initOK = false;
638
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 if (protocol == "ws2811") {
639
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 initOK = InitializeWS281x();
640 }
641
642
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (!initOK) {
643 ✗ LogErr(VB_CHANNELOUT, "Error initializing pixel protocol, Channel Output will be disabled.\n");
644 ✗ WarningHolder::AddWarning("DPIPixels: Error initializing pixel protocol");
645 ✗ return 0;
646 }
647
648
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 PixelString::AutoCreateOverlayModels(pixelStrings, m_autoCreatedModelNames);
649
650 // Write blank WS281x data to framebuffer BEFORE configuring pins as DPI
651 // This prevents garbage/flash when pins start outputting
652
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 if (protocol == "ws2811") {
653
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
26 std::vector<unsigned char> blankData(FPPD_MAX_CHANNELS, 0);
654
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 PrepData(blankData.data());
655
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 CopyShadowToAllPages();
656 13 fbPage = 0;
657 13 }
658
659 // Configure DPI pins - they will output blank data from framebuffer
660
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 13 times.
39 for (const auto& pinName : m_configuredDPIPins) {
661 try {
662
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 LogDebug(VB_CHANNELOUT, "Enabling pin %s for DPI\n", pinName.c_str());
663
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 const PinCapabilities& pin = PinCapabilities::getPinByName(pinName);
664
3/6
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
78 pin.configPin("dpi", true, "DPIPixels-" + pinName);
665 ✗ } catch (const std::exception& ex) {
666 ✗ LogErr(VB_CHANNELOUT, "Failed to configure %s as DPI: %s\n", pinName.c_str(), ex.what());
667 ✗ }
668 }
669
670 #ifdef LOGIC_ANALYZING
671 // Turn on the HSync and VSync pins for logic analyzing
672 const PinCapabilities& hs_pin = PinCapabilities::getPinByName("P1-5");
673 hs_pin.configPin("dpi");
674 const PinCapabilities& vs_pin = PinCapabilities::getPinByName("P1-3");
675 vs_pin.configPin("dpi");
676 #endif
677
678 // Enable runtime refresh-rate control now that pins are live.
679 13 m_initialized = true;
680
681
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 return ChannelOutput::Init(config);
682 14 }
683
684 14 int DPIPixelsOutput::IdleFpsFromSetting() {
685 14 int intervalMS = getSettingInt("E131BridgingInterval", 50);
686
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 return intervalMS > 0 ? std::max(1, (int)std::lround(1000.0 / intervalMS)) : 20;
687 }
688
689 13 int DPIPixelsOutput::Close(void) {
690 13 LogDebug(VB_CHANNELOUT, "DPIPixelsOutput::Close()\n");
691
3/6
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
39 unregisterSettingsListener("DPIPixels", "E131BridgingInterval");
692
693 // Stop runtime rate control so the blanking PrepData() calls below don't
694 // trigger a modeset while we're tearing down.
695 13 m_initialized = false;
696
697 // Send blank WS281x data to clear latched pixels before shutdown
698
4/8
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 13 times.
✗ Branch 7 not taken.
13 if (fb && pixelStrings.size() > 0 && protocol == "ws2811") {
699
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
26 std::vector<unsigned char> blankData(FPPD_MAX_CHANNELS, 0);
700
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 PrepData(blankData.data());
701 // Deliberately write both pages, the live one included: at shutdown
702 // there is no flip coming, so the blank reaches the wire only by
703 // overwriting whatever the DPI is scanning. Tearing is harmless here
704 // (both halves of a torn frame are dark or about to be), and this is
705 // the one place the "never write the page being scanned" rule is
706 // waived - see CopyShadowToAllPages(). Then wait two full scans at
707 // the live rate so the last frame out is entirely the blank one.
708
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 CopyShadowToAllPages();
709 13 fbPage = 0;
710
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 int periodMS = m_currentFps > 0 ? (1000 / m_currentFps) : 50;
711
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 std::this_thread::sleep_for(std::chrono::milliseconds(2 * periodMS + 10));
712 13 }
713
714 // Reconfigure DPI pins back to GPIO input
715
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 13 times.
39 for (const auto& pinName : m_configuredDPIPins) {
716 try {
717
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 const PinCapabilities& pin = PinCapabilities::getPinByName(pinName);
718
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 pin.releasePin();
719 ✗ } catch (...) {
720 ✗ }
721 }
722 13 m_configuredDPIPins.clear();
723
724
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 13 times.
35 for (auto& n : m_autoCreatedModelNames) {
725
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 PixelOverlayManager::INSTANCE.removeAutoOverlayModel(n);
726 }
727 13 return ChannelOutput::Close();
728 }
729
730 26 void DPIPixelsOutput::GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) {
731 26 PixelString* ps = NULL;
732
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 26 times.
74 for (int s = 0; s < pixelStrings.size(); s++) {
733 48 ps = pixelStrings[s];
734 48 int inCh = 0;
735 48 int min = FPPD_MAX_CHANNELS;
736 48 int max = 0;
737
2/2
✓ Branch 0 taken 20520 times.
✓ Branch 1 taken 48 times.
20568 for (int p = 0; p < ps->m_outputBytes; p++) {
738 20520 int ch = ps->m_outputMap[inCh++];
739
1/2
✓ Branch 0 taken 20520 times.
✗ Branch 1 not taken.
20520 if (ch < FPPD_MAX_CHANNELS) {
740 20520 min = std::min(min, ch);
741 20520 max = std::max(max, ch);
742 }
743 }
744
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 if (min < max) {
745
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 addRange(min, max);
746 }
747 }
748 26 }
749
750 ✗ void DPIPixelsOutput::OverlayTestData(unsigned char* channelData, int cycleNum, float percentOfCycle, int testType, const Json::Value& config) {
751 ✗ m_testCycle = cycleNum;
752 ✗ m_testType = testType;
753 ✗ m_testPercent = percentOfCycle;
754
755 // We won't overlay the data here because we could have multiple strings
756 // pointing at the same channel range so a per-port test cannot
757 // be done via channel ranges. We'll record the test information and use
758 // that in prepData
759 ✗ }
760
761 818 void DPIPixelsOutput::PrepData(unsigned char* channelData) {
762 // Match the DPI output rate to the running sequence, capped by what the
763 // longest string physically allows. A change only happens when a sequence
764 // with a different frame rate starts, so the (brief) modeset is rare.
765 //
766 // With no sequence running, the output thread runs at the E1.31 bridging
767 // interval (overlays, effects, bridge data, test mode), so after a settle
768 // window the DPI is retimed to that rate rather than left at whatever the
769 // last sequence used. Only change after the settle window, though: a rate
770 // change is a full drmModeSetCrtc that drains the pending flip and restarts
771 // the vblank stream (cutting the WS stream mid-frame), so bouncing on the
772 // brief gaps between playlist items would glitch the output every time.
773 // Tracking a sequence's rate stays immediate -- that has to follow the data.
774 // A current rate that is an exact multiple of the wanted one is kept,
775 // so the common 20/40 fps mix never modesets at all.
776
3/4
✓ Branch 0 taken 792 times.
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 792 times.
✗ Branch 3 not taken.
818 if (m_initialized && m_configuredMaxFps > 0) {
777 792 constexpr long long IDLE_SETTLE_US = 3000000;
778
779
1/2
✓ Branch 0 taken 792 times.
✗ Branch 1 not taken.
792 int target = (int)std::lround(GetChannelOutputRefreshRate());
780
1/2
✓ Branch 0 taken 792 times.
✗ Branch 1 not taken.
792 bool idle = !sequence->IsSequenceRunning();
781
2/2
✓ Branch 0 taken 546 times.
✓ Branch 1 taken 246 times.
792 if (!idle) {
782 546 m_idleSinceUS = 0;
783 } else {
784
1/2
✓ Branch 0 taken 246 times.
✗ Branch 1 not taken.
246 long long now = GetTime();
785
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 227 times.
246 if (m_idleSinceUS == 0) {
786 19 m_idleSinceUS = now;
787 }
788
2/2
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 196 times.
246 if ((now - m_idleSinceUS) >= IDLE_SETTLE_US) {
789 // With no sequence running the output thread produces frames
790 // at the E1.31 bridging interval, so that is the rate to scan
791 // at; anything faster only rescans the same page. A current
792 // rate that is an exact multiple of it is kept below.
793 50 target = m_idleFps;
794 }
795 }
796
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 792 times.
792 if (target < 1) {
797 ✗ target = 1;
798 }
799
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 792 times.
792 if (target > m_configuredMaxFps) {
800 ✗ target = m_configuredMaxFps;
801 }
802 // A refresh that is an exact multiple of the wanted rate is as good as
803 // the wanted rate: each frame is just scanned N times and the flip
804 // still lands on a vblank. Keep it and skip the modeset, which cuts
805 // the DPI stream mid-frame every time it runs.
806
4/4
✓ Branch 0 taken 155 times.
✓ Branch 1 taken 637 times.
✓ Branch 2 taken 64 times.
✓ Branch 3 taken 91 times.
792 if (m_currentFps > target && (m_currentFps % target) == 0) {
807 64 target = m_currentFps;
808 }
809
7/8
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 694 times.
✓ Branch 2 taken 98 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 90 times.
✓ Branch 6 taken 8 times.
✓ Branch 7 taken 784 times.
792 if (target != m_currentFps && fb->SetRefreshRate(target)) {
810
4/6
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
8 LogInfo(VB_CHANNELOUT, "DPIPixels: DPI refresh -> %d fps (%s, sequence rate %.1f, max %d)\n",
811 target, idle ? "idle" : "playing",
812 GetChannelOutputRefreshRate(), m_configuredMaxFps);
813 8 m_currentFps = target;
814 }
815 }
816
817 818 PixelString* ps = NULL;
818 818 int ch = 0;
819 #ifdef LOG_ELAPSED_TIME
820 long long startTime = 0;
821 long long elapsedTimeGather = 0;
822 long long elapsedTimeTranspose = 0;
823 long long elapsedTimeOutput = 0;
824 #endif
825
826 #ifdef USE_AUTO_SYNC
827 fbPage = fb->Page(true);
828
829 if (fb->IsDirty(fbPage)) {
830 LogErr(VB_CHANNELOUT, "FB Page %d is dirty, output thread isn't keeping up!\n", fbPage);
831 fbPage = -1;
832 return;
833 }
834
835 fb->NextPage(true);
836 #else
837 818 fbPage = fb->Page();
838 #endif
839
840 818 protoBitOnLine = 0;
841
842 uint8_t* outputBuffers[80];
843 818 PixelStringTester* tester = nullptr;
844
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 818 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
818 if (m_testType && m_testCycle >= 0) {
845 ✗ tester = PixelStringTester::getPixelStringTester(m_testType);
846 // null for a test type no string tester implements; the strings then
847 // just send their normal data
848 ✗ if (tester) {
849 ✗ tester->prepareTestData(m_testCycle, m_testPercent);
850 }
851 }
852
2/2
✓ Branch 0 taken 65440 times.
✓ Branch 1 taken 818 times.
66258 for (int x = 0; x < 80; x++) {
853
2/2
✓ Branch 0 taken 1623 times.
✓ Branch 1 taken 63817 times.
65440 if (x < pixelStrings.size()) {
854 1623 ps = pixelStrings[x];
855 1623 uint32_t newLen = 0;
856 1623 outputBuffers[x] = tester
857
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1623 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1623 ? tester->createTestData(ps, m_testCycle, m_testPercent, channelData, newLen)
858
1/2
✓ Branch 0 taken 1623 times.
✗ Branch 1 not taken.
1623 : ps->prepareOutput(channelData);
859 } else {
860 63817 outputBuffers[x] = nullptr;
861 }
862 }
863
864 // Start at the first data row of the shadow page (after the blank lead
865 // rows) but skip the first third of the WS bit which is already populated
866 // and doesn't change
867 818 protoDest = m_shadow + (DPI_LEAD_ROWS * fb->RowStride()) +
868
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 811 times.
818 ((usingLatches ? latchDataPhasePx(latchCount) : DPI_T0H_PX) * fb->BytesPerPixel());
869
870 uint32_t dataIn[MAX_DPI_PIXEL_LATCHES][32];
871 uint32_t dataOut[MAX_DPI_PIXEL_LATCHES][32];
872 818 int lineOffset = fb->RowPadding();
873 818 int output = 0;
874
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 811 times.
818 int dataSets = usingLatches ? MAX_DPI_PIXEL_LATCHES : 1;
875
876 #ifdef LOG_ELAPSED_TIME
877 startTime = GetTime();
878 #endif
879
880 // Write data out 4 channels (32 WS bits) at a time
881
2/2
✓ Branch 0 taken 14888 times.
✓ Branch 1 taken 818 times.
15706 for (int y0 = 0, y1 = 1, y2 = 2, y3 = 3; y0 < longestString; y0 += 4, y1 += 4, y2 += 4, y3 += 4) {
882 14888 memset(dataIn, 0, sizeof(dataIn));
883
884 // Storing up to 32 bits of data for up to 24 outputs (or 24 per latch)
885
2/2
✓ Branch 0 taken 40088 times.
✓ Branch 1 taken 14888 times.
54976 for (int lp = 0; lp < dataSets; lp++) {
886
2/2
✓ Branch 0 taken 962112 times.
✓ Branch 1 taken 40088 times.
1002200 for (int o = 0; o < 24; o++) {
887 962112 int strNum = (lp * 24) + o;
888 962112 output = outputToStringMap[strNum];
889
4/4
✓ Branch 0 taken 29776 times.
✓ Branch 1 taken 932336 times.
✓ Branch 2 taken 29672 times.
✓ Branch 3 taken 104 times.
962112 if ((output != -1) && (outputBuffers[output])) {
890 29672 dataIn[lp][31 - o] =
891
1/2
✓ Branch 0 taken 29672 times.
✗ Branch 1 not taken.
29672 (y0 >= stringLengths[strNum] ? 0 : ((uint32_t)outputBuffers[output][y0] << 24)) |
892
1/2
✓ Branch 0 taken 29672 times.
✗ Branch 1 not taken.
29672 (y1 >= stringLengths[strNum] ? 0 : ((uint32_t)outputBuffers[output][y1] << 16)) |
893
2/2
✓ Branch 0 taken 28063 times.
✓ Branch 1 taken 1609 times.
59344 (y2 >= stringLengths[strNum] ? 0 : ((uint32_t)outputBuffers[output][y2] << 8)) |
894
2/2
✓ Branch 0 taken 28063 times.
✓ Branch 1 taken 1609 times.
29672 (y3 >= stringLengths[strNum] ? 0 : ((uint32_t)outputBuffers[output][y3]));
895 }
896 }
897 }
898
899 #ifdef LOG_ELAPSED_TIME
900 elapsedTimeGather += GetTime() - startTime;
901 startTime = GetTime();
902 #endif
903
904
2/2
✓ Branch 0 taken 40088 times.
✓ Branch 1 taken 14888 times.
54976 for (int lp = 0; lp < dataSets; lp++) {
905
1/2
✓ Branch 0 taken 40088 times.
✗ Branch 1 not taken.
40088 TransposeBits32x32(dataOut[lp], dataIn[lp]);
906 }
907
908 #ifdef LOG_ELAPSED_TIME
909 elapsedTimeTranspose += GetTime() - startTime;
910 startTime = GetTime();
911 #endif
912
913 // Write 4 channels (32 bits) of data for 24 outputs (or 24 per latch)
914 14888 int ch = 0;
915
2/2
✓ Branch 0 taken 476416 times.
✓ Branch 1 taken 14888 times.
491304 for (int bit = 0; bit < 32; bit++) {
916
2/2
✓ Branch 0 taken 268800 times.
✓ Branch 1 taken 207616 times.
476416 if (usingLatches) {
917 268800 ch = y0 + (bit / 8);
918
2/2
✓ Branch 0 taken 1075200 times.
✓ Branch 1 taken 268800 times.
1344000 for (int lp = 0; lp < dataSets; lp++) {
919
2/2
✓ Branch 0 taken 537600 times.
✓ Branch 1 taken 537600 times.
1075200 if (lp < latchCount) {
920
1/2
✓ Branch 0 taken 537600 times.
✗ Branch 1 not taken.
537600 WriteLatchedDataAtPosition(protoDest, (uint32_t)dataOut[lp][bit], latchPinMasks[lp]);
921 } else {
922 537600 protoDest += 4 * fb->BytesPerPixel();
923 }
924 }
925 } else {
926
1/2
✓ Branch 0 taken 207616 times.
✗ Branch 1 not taken.
207616 WriteDataAtPosition(protoDest, (uint32_t)dataOut[0][bit]);
927 }
928
929 476416 protoBitOnLine++;
930
931 // Skip the static last third of this WS BIT and first third of next WS bit
932 // and position at middle third of next WS bit
933 476416 protoDest += fb->BytesPerPixel() * fbPixelMult * 2;
934
935 // When necessary, jump to middle third of first WS bit on next line
936
2/2
✓ Branch 0 taken 11586 times.
✓ Branch 1 taken 464830 times.
476416 if (protoBitOnLine >= protoBitsPerLine) {
937 11586 protoDest += lineOffset;
938 11586 protoBitOnLine = 0;
939 }
940 }
941
942 #ifdef LOG_ELAPSED_TIME
943 elapsedTimeOutput += GetTime() - startTime;
944 #endif
945 }
946
947 #ifdef LOG_ELAPSED_TIME
948 LogInfo(VB_CHANNELOUT, "Data Gather : %lldus\n", elapsedTimeGather);
949 LogInfo(VB_CHANNELOUT, "Data Transpose : %lldus\n", elapsedTimeTranspose);
950 LogInfo(VB_CHANNELOUT, "Data Output : %lldus\n", elapsedTimeOutput);
951 LogInfo(VB_CHANNELOUT, "PrepData() Total: %lldus\n", elapsedTimeGather + elapsedTimeTranspose + elapsedTimeOutput);
952 #endif
953 818 }
954
955 // Writes every page, including the one currently being scanned. Only valid
956 // while the pins are not yet live (Init, template build) or when a torn frame
957 // is acceptable (the shutdown blank in Close()). Do not add WaitForPageFree()
958 // here: SendData() is the per-frame path and already waits for the free page.
959 39 void DPIPixelsOutput::CopyShadowToAllPages() {
960
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 39 times.
117 for (int page = 0; page < fb->PageCount(); page++) {
961 78 memcpy(fb->BufferPage(page), m_shadow, m_shadowCopyBytes);
962 }
963 39 }
964
965 767 int DPIPixelsOutput::SendData(unsigned char* channelData) {
966
1/2
✓ Branch 0 taken 767 times.
✗ Branch 1 not taken.
767 if (fbPage >= 0) {
967 // The page PrepData() targeted was being scanned out until the
968 // previous flip retired; make sure it has before overwriting it.
969 767 fb->WaitForPageFree();
970 767 memcpy(fb->BufferPage(fbPage), m_shadow, m_shadowCopyBytes);
971 }
972 #ifdef USE_AUTO_SYNC
973 if (fbPage >= 0) {
974 // LogInfo(VB_CHANNELOUT, "%d - SendData() marking page dirty\n", fbPage);
975 fb->SetDirty(fbPage);
976 fbPage = -1;
977 }
978 #else
979 767 fb->SyncDisplay(true);
980 767 fb->NextPage();
981 #endif
982
983 767 return m_channelCount;
984 }
985
986 13 void DPIPixelsOutput::DumpConfig(void) {
987 13 LogDebug(VB_CHANNELOUT, "DPIPixelsOutput::DumpConfig()\n");
988
989 13 LogDebug(VB_CHANNELOUT, "protoBitsPerLine : %d\n", protoBitsPerLine);
990 13 LogDebug(VB_CHANNELOUT, "protoDestExtra : %d\n", protoDestExtra);
991 13 LogDebug(VB_CHANNELOUT, "longestString : %d (channels)\n", longestString);
992
993 13 LogDebug(VB_CHANNELOUT, "Strings:\n");
994
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 13 times.
37 for (int i = 0; i < pixelStrings.size(); i++) {
995 24 LogDebug(VB_CHANNELOUT, " String #%d\n", i);
996 24 pixelStrings[i]->DumpConfig();
997 }
998
999 13 ChannelOutput::DumpConfig();
1000 13 }
1001
1002 29 int DPIPixelsOutput::GetDPIPinBitPosition(std::string pinName) {
1003 29 int bitPosition = -1;
1004
1005 // https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#gpio-pins
1006 // /dev/fb* is BGR order even though we are using RGB888
1007 // The FB color order is handled inside Write(Latched)DataAtPosition(). Internally, we
1008 // use RGB in the lower 24 bits of uint32_t.
1009
1010 // clang-format off
1011 // Blue in RGB888 // DPI Pin - GPIO # - Position in RGB888
1012
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 14 times.
29 if (pinName == "P1-7") bitPosition = 0; // DPI_D0 - GPIO4 - 0x000001
1013
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 1 times.
14 else if (pinName == "P1-29") bitPosition = 1; // DPI_D1 - GPIO5 - 0x000002
1014
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 else if (pinName == "P1-31") bitPosition = 2; // DPI_D2 - GPIO6 - 0x000004
1015 ✗ else if (pinName == "P1-26") bitPosition = 3; // DPI_D3 - GPIO7 - 0x000008
1016 ✗ else if (pinName == "P1-24") bitPosition = 4; // DPI_D4 - GPIO8 - 0x000010
1017 ✗ else if (pinName == "P1-21") bitPosition = 5; // DPI_D5 - GPIO9 - 0x000020
1018 ✗ else if (pinName == "P1-19") bitPosition = 6; // DPI_D6 - GPIO10 - 0x000040
1019 ✗ else if (pinName == "P1-23") bitPosition = 7; // DPI_D7 - GPIO11 - 0x000080
1020 // Green in RGB888
1021 ✗ else if (pinName == "P1-32") bitPosition = 8; // DPI_D8 - GPIO12 - 0x000100
1022 ✗ else if (pinName == "P1-33") bitPosition = 9; // DPI_D9 - GPIO13 - 0x000200
1023 ✗ else if (pinName == "P1-8") bitPosition = 10; // DPI_D10 - GPIO14 - 0x000400
1024 ✗ else if (pinName == "P1-10") bitPosition = 11; // DPI_D11 - GPIO15 - 0x000800
1025 ✗ else if (pinName == "P1-36") bitPosition = 12; // DPI_D12 - GPIO16 - 0x001000
1026 ✗ else if (pinName == "P1-11") bitPosition = 13; // DPI_D13 - GPIO17 - 0x002000
1027 ✗ else if (pinName == "P1-12") bitPosition = 14; // DPI_D14 - GPIO18 - 0x004000
1028 ✗ else if (pinName == "P1-35") bitPosition = 15; // DPI_D15 - GPIO19 - 0x008000
1029 // Red in RGB888
1030 ✗ else if (pinName == "P1-38") bitPosition = 16; // DPI_D16 - GPIO20 - 0x010000
1031 ✗ else if (pinName == "P1-40") bitPosition = 17; // DPI_D17 - GPIO21 - 0x020000
1032 ✗ else if (pinName == "P1-15") bitPosition = 18; // DPI_D18 - GPIO22 - 0x040000
1033 ✗ else if (pinName == "P1-16") bitPosition = 19; // DPI_D19 - GPIO23 - 0x080000
1034 ✗ else if (pinName == "P1-18") bitPosition = 20; // DPI_D20 - GPIO24 - 0x100000
1035 ✗ else if (pinName == "P1-22") bitPosition = 21; // DPI_D21 - GPIO25 - 0x200000
1036 ✗ else if (pinName == "P1-37") bitPosition = 22; // DPI_D22 - GPIO26 - 0x400000
1037 ✗ else if (pinName == "P1-13") bitPosition = 23; // DPI_D23 - GPIO27 - 0x800000
1038 // clang-format on
1039
1040 29 return bitPosition;
1041 }
1042
1043 13 bool DPIPixelsOutput::FrameBufferIsConfigured(void) {
1044
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (!fb)
1045 ✗ return false;
1046
1047
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
13 if (protocol != "ws2811")
1048 ✗ return false;
1049
1050
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 std::string errStr = "";
1051
1052 // The DPI overlay (config.txt: dtoverlay=vc4-kms-dpi-fpp) fixes the width at
1053 // 1920 and the pixel clock at 38.4MHz, which is what makes each WS bit 48 FB
1054 // pixels (1.25us). Without it the bit timing is wrong.
1055
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (fb->Width() != 1920) {
1056 ✗ errStr = m_outputType + " Framebuffer width " + std::to_string(fb->Width()) +
1057 ✗ " is not 1920; the DPI overlay is not configured. A reboot may be required to load it.";
1058
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
13 } else if (!fb->SupportsVariableRefresh()) {
1059 // Non-KMS fallback (e.g. IOCTL fbdev) cannot retime the output; the
1060 // variable frame rate feature requires the KMS DPI device.
1061 ✗ errStr = m_outputType + " Framebuffer does not support variable refresh; KMS DPI output is required.";
1062
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 } else if (longestString > 4800) {
1063 // Hard limit from onOffMask[][4800] (1600 pixels per string).
1064 ✗ errStr = m_outputType + " channel count " + std::to_string(longestString) +
1065 ✗ " is too high. Max 4800 channels (1600 pixels) per string.";
1066 } else {
1067 13 return true;
1068 }
1069
1070 ✗ WarningHolder::AddWarning(errStr);
1071 ✗ errStr += "\n";
1072 ✗ LogErr(VB_CHANNELOUT, errStr);
1073 ✗ return false;
1074 13 }
1075
1076 ////////////////////////////////////////////////
1077 // Protocol-specific functions
1078 ////////////////////////////////////////////////
1079
1080 13 bool DPIPixelsOutput::InitializeWS281x(void) {
1081 13 uint32_t onOff = 0x000000;
1082 13 int y = 0;
1083 13 int strPixel = 0;
1084 13 int sStart = 0;
1085 13 int sEnd = pixelStrings.size();
1086 uint32_t pinsOn[MAX_DPI_PIXEL_LATCHES];
1087
1088
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 fb->ClearAllPages();
1089 13 memset(m_shadow, 0, fb->PageSize());
1090
1091 // 16 FB pixels per third of WS bit gives us room to turn on/off 4 sets of latches
1092 13 fbPixelMult = 16; // @ 38.4Mhz, 1920 FB width
1093
1094 13 protoBitOnLine = 0;
1095
1096 // Each WS bit is split into three chunks of fbPixelMult FB pixels
1097 13 protoBitsPerLine = fb->Width() / (3 * fbPixelMult);
1098
1099 // Build the static template in the shadow page; it is copied to the
1100 // (uncached) KMS pages below and only its middle thirds change afterwards.
1101 // The lead rows stay all-zero so the frame starts with a fully low line.
1102 13 protoDest = m_shadow + (DPI_LEAD_ROWS * fb->RowStride());
1103
1104 // Skip over the hsync/porch pad area
1105 13 protoDestExtra = fb->RowPadding();
1106
1107 #ifdef LOG_ELAPSED_TIME
1108 long long endTime = 0;
1109 long long elapsed = 0;
1110 long long startTime = GetTime();
1111 #endif
1112
1113
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 12 times.
13 if (usingLatches) {
1114 1 uint32_t nonLatchPins = 0xFFFFFF;
1115 // Round up to next multiple of 4 since we will later write out data 4 channels at a time.
1116 1 int lastChannel = (longestString + 3) & ~0x03;
1117
1118
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 for (int lp = 0; lp < MAX_DPI_PIXEL_LATCHES; lp++) {
1119 4 nonLatchPins -= latchPinMasks[lp];
1120 }
1121
1122 1 memset(pinsOn, 0, sizeof(pinsOn));
1123
1124
2/2
✓ Branch 0 taken 4800 times.
✓ Branch 1 taken 1 times.
4801 for (int y = 0; y < lastChannel; y++) {
1125 // Figure out which pins are allowed to be on for each latch
1126
2/2
✓ Branch 0 taken 9600 times.
✓ Branch 1 taken 4800 times.
14400 for (int lp = 0; lp < latchCount; lp++) {
1127 9600 pinsOn[lp] = nonLatchPins & onOffMask[lp][y];
1128
1129 // Force pins off where the string length is less than the current channel
1130
2/2
✓ Branch 0 taken 230400 times.
✓ Branch 1 taken 9600 times.
240000 for (int p = 0; p < 24; p++) {
1131 230400 int outputNumber = outputToStringMap[(lp * 24) + p];
1132
2/2
✓ Branch 0 taken 220800 times.
✓ Branch 1 taken 9600 times.
230400 if (y >= stringLengths[(lp * 24) + p]) {
1133 220800 pinsOn[lp] = pinsOn[lp] & ~POSITION_TO_BITMASK(p);
1134 }
1135 }
1136 }
1137
1138 // Populate 8 bits for each channel. Each WS bit is a 48 px cell
1139 // holding three phases of latch pulses; where the data and low
1140 // phases start depends on the bank count (see latchDataPhasePx).
1141 4800 const int bitBytes = fbPixelMult * 3 * fb->BytesPerPixel();
1142 4800 const int dataPhaseBytes = latchDataPhasePx(latchCount) * fb->BytesPerPixel();
1143 4800 const int lowPhaseBytes = latchLowPhasePx(latchCount) * fb->BytesPerPixel();
1144
2/2
✓ Branch 0 taken 38400 times.
✓ Branch 1 taken 4800 times.
43200 for (int b = 0; b < 8; b++) {
1145 38400 uint8_t* bitStart = protoDest;
1146
1147 // "Go high" phase at the start of the cell. Never modified later.
1148 // Stays high whether WS bit is a 0 or 1.
1149
2/2
✓ Branch 0 taken 76800 times.
✓ Branch 1 taken 38400 times.
115200 for (int lp = 0; lp < latchCount; lp++) {
1150
1/2
✓ Branch 0 taken 76800 times.
✗ Branch 1 not taken.
76800 WriteLatchedDataAtPosition(protoDest, pinsOn[lp], latchPinMasks[lp]);
1151 }
1152
1153 // "Go to data" phase: low or high depending on whether the WS
1154 // bit is a 0 or 1. Set to low initially; PrepData() rewrites it.
1155 38400 protoDest = bitStart + dataPhaseBytes;
1156
2/2
✓ Branch 0 taken 76800 times.
✓ Branch 1 taken 38400 times.
115200 for (int lp = 0; lp < latchCount; lp++) {
1157
1/2
✓ Branch 0 taken 76800 times.
✗ Branch 1 not taken.
76800 WriteLatchedDataAtPosition(protoDest, 0x000000, latchPinMasks[lp]);
1158 }
1159
1160 // "Go low" phase. Never modified later.
1161 38400 protoDest = bitStart + lowPhaseBytes;
1162
2/2
✓ Branch 0 taken 76800 times.
✓ Branch 1 taken 38400 times.
115200 for (int lp = 0; lp < latchCount; lp++) {
1163
1/2
✓ Branch 0 taken 76800 times.
✗ Branch 1 not taken.
76800 WriteLatchedDataAtPosition(protoDest, 0x000000, latchPinMasks[lp]);
1164 }
1165
1166 38400 protoDest = bitStart + bitBytes;
1167 38400 protoBitOnLine++;
1168
1169
2/2
✓ Branch 0 taken 960 times.
✓ Branch 1 taken 37440 times.
38400 if (protoBitOnLine >= protoBitsPerLine) {
1170 960 protoDest += protoDestExtra;
1171 960 protoBitOnLine = 0;
1172 }
1173 }
1174 }
1175 } else {
1176 // NOT using latches
1177 12 latchPinMask = 0x000000;
1178
1179
2/2
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 12 times.
84 while (y < longestString) {
1180 // For each WS281x bit on the scan line
1181
2/2
✓ Branch 0 taken 360 times.
✓ Branch 1 taken 72 times.
432 for (int x = 0; x < protoBitsPerLine; x += 8) {
1182 // For each bit in the WS281x pixel protocol
1183
2/2
✓ Branch 0 taken 2880 times.
✓ Branch 1 taken 360 times.
3240 for (int rp = 0; rp < 8; rp++) {
1184
1/2
✓ Branch 0 taken 2880 times.
✗ Branch 1 not taken.
2880 if (licensedOutputs) {
1185
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2880 times.
2880 if (usingSmartReceivers) {
1186 ✗ onOff = 0x000000;
1187 ✗ for (int s = sStart; s < sEnd; s++) {
1188 ✗ if ((bitPos[s - sStart] != -1) && (onOffMap[strPixel * pixelStrings.size() + s]))
1189 ✗ onOff |= POSITION_TO_BITMASK(bitPos[s - sStart]);
1190 }
1191 } else {
1192 2880 onOff = 0xFFFFFF;
1193 }
1194 } else {
1195 ✗ onOff = 0xFFFFFF;
1196 }
1197
1198 // Update the FB pixels making up the leading high of the WS
1199 // bit (T0H). These FB pixels will never be modified again.
1200
2/2
✓ Branch 0 taken 34560 times.
✓ Branch 1 taken 2880 times.
37440 for (int i = 0; i < DPI_T0H_PX; i++) {
1201 34560 *(protoDest++) = (onOff >> 16);
1202 34560 *(protoDest++) = (onOff >> 8);
1203 34560 *(protoDest++) = (onOff);
1204 }
1205
1206 // Skip over the rest of the current WS bit. The 16 px data
1207 // section right after the leading high gets updated in
1208 // PrepData() later and the remainder is always 0x000000.
1209 2880 protoDest += fb->BytesPerPixel() * (fbPixelMult * 3 - DPI_T0H_PX);
1210 }
1211
1212 360 y++;
1213 360 strPixel++;
1214 }
1215
1216 // Skips any row padding
1217 72 protoDest += protoDestExtra;
1218 }
1219
1220 // Inside PrepData(), we'll skip an extra two-thirds WS BIT pixels due to the way it works
1221 12 protoDestExtra += fb->BytesPerPixel() * fbPixelMult * 2;
1222
1223 // int perLine = fb->BytesPerPixel() * fbPixelMult * 3;
1224 // int header = fb->BytesPerPixel() * sBeginEndSize;
1225 // int numLines = 76 * 8;
1226 // HexDump("fb data:", fb->BufferPage(0) + header, numLines * perLine, VB_CHANNELOUT, perLine);
1227 }
1228
1229 #ifdef LOG_ELAPSED_TIME
1230 endTime = GetTime();
1231 elapsed = endTime - startTime;
1232 LogInfo(VB_CHANNELOUT, "InitializeWS2811 Elapsed: %lld\n", elapsed);
1233 #endif
1234
1235
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 CopyShadowToAllPages();
1236
1237
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 fb->SyncDisplay(true);
1238
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 fb->NextPage();
1239
1240 13 return true;
1241 }
1242