| 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 |