| 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 <sys/wait.h> | ||
| 19 | #include <unistd.h> | ||
| 20 | |||
| 21 | #include <arm_neon.h> | ||
| 22 | |||
| 23 | #include <algorithm> | ||
| 24 | |||
| 25 | #include <fstream> | ||
| 26 | |||
| 27 | #define BBB_PRU 1 | ||
| 28 | |||
| 29 | // #define PRINT_STATS | ||
| 30 | |||
| 31 | // FPP includes | ||
| 32 | #include "../../Sequence.h" | ||
| 33 | #include "../FalconV5Support/FalconV5Support.h" | ||
| 34 | #include "../../Warnings.h" | ||
| 35 | #include "../../channeloutput/ChannelOutputSetup.h" | ||
| 36 | #include "../../common.h" | ||
| 37 | #include "../../log.h" | ||
| 38 | |||
| 39 | #include "BBB48String.h" | ||
| 40 | #include "../CapeUtils/CapeUtils.h" | ||
| 41 | #include "channeloutput/stringtesters/PixelStringTester.h" | ||
| 42 | #include "util/BBBUtils.h" | ||
| 43 | |||
| 44 | #include "../../overlays/PixelOverlay.h" | ||
| 45 | |||
| 46 | #include "Plugin.h" | ||
| 47 | class BBB48StringPlugin : public FPPPlugins::Plugin, | ||
| 48 | public FPPPlugins::ChannelOutputPlugin { | ||
| 49 | public: | ||
| 50 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
75 | BBB48StringPlugin() : FPPPlugins::Plugin("BBB48String") {} |
| 51 | virtual ChannelOutput* | ||
| 52 | 28 | createChannelOutput(unsigned int startChannel, | |
| 53 | unsigned int channelCount) override { | ||
| 54 |
2/6✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 28 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
56 | return new BBB48StringOutput(startChannel, channelCount); |
| 55 | } | ||
| 56 | }; | ||
| 57 | |||
| 58 | extern "C" { | ||
| 59 |
2/6✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 25 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
50 | FPPPlugins::Plugin* createPlugin() { return new BBB48StringPlugin(); } |
| 60 | } | ||
| 61 | |||
| 62 | /* | ||
| 63 | * | ||
| 64 | */ | ||
| 65 | 28 | BBB48StringOutput::BBB48StringOutput(unsigned int startChannel, | |
| 66 | 28 | unsigned int channelCount) : ChannelOutput(startChannel, channelCount), | |
| 67 | 28 | m_curFrame(0), | |
| 68 | 28 | m_pru(NULL), | |
| 69 | 28 | m_pruData(NULL), | |
| 70 | 28 | m_pru0(NULL), | |
| 71 | 28 | m_pru0Data(NULL), | |
| 72 | 28 | m_stallCount(0) { | |
| 73 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | LogDebug(VB_CHANNELOUT, "BBB48StringOutput::BBB48StringOutput(%u, %u)\n", |
| 74 | startChannel, channelCount); | ||
| 75 | 28 | } | |
| 76 | |||
| 77 | /* | ||
| 78 | * | ||
| 79 | */ | ||
| 80 | 112 | BBB48StringOutput::~BBB48StringOutput() { | |
| 81 | 56 | LogDebug(VB_CHANNELOUT, "BBB48StringOutput::~BBB48StringOutput()\n"); | |
| 82 |
2/2✓ Branch 0 taken 163 times.
✓ Branch 1 taken 28 times.
|
382 | for (auto a : m_strings) { |
| 83 |
1/2✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
|
326 | delete a; |
| 84 | } | ||
| 85 | 56 | m_strings.clear(); | |
| 86 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
|
56 | if (m_pru) |
| 87 | ✗ | delete m_pru; | |
| 88 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
|
56 | if (m_pru0) |
| 89 | ✗ | delete m_pru0; | |
| 90 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 26 times.
|
56 | if (falconV5Support) |
| 91 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
4 | delete falconV5Support; |
| 92 | 112 | } | |
| 93 | |||
| 94 | 29 | static void compilePRUCode(const char* program, | |
| 95 | const std::vector<std::string>& sargs, | ||
| 96 | const std::vector<std::string>& args1) { | ||
| 97 |
1/2✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
|
29 | std::vector<std::string> args = sargs; |
| 98 |
1/2✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
|
29 | args.insert(args.end(), args1.begin(), args1.end()); |
| 99 |
2/4✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
|
29 | CompilePRUProgram(program, args); |
| 100 | 29 | } | |
| 101 | |||
| 102 | 25 | static void compilePRUCode(const std::vector<std::string>& sargs, | |
| 103 | const std::vector<std::string>& args0, | ||
| 104 | const std::vector<std::string>& args1, bool split) { | ||
| 105 | 25 | compilePRUCode("/opt/fpp/src/non-gpl/BBB48String/compileWS2811x.sh", sargs, | |
| 106 | args1); | ||
| 107 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
|
25 | if (split) { |
| 108 | 4 | compilePRUCode("/opt/fpp/src/non-gpl/BBB48String/compileWS2811x_gpio0.sh", | |
| 109 | sargs, args0); | ||
| 110 | } | ||
| 111 | 25 | } | |
| 112 | |||
| 113 | 54 | inline int mapSize(int max, int maxString) { | |
| 114 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 54 times.
|
54 | if (maxString > max) { |
| 115 | ✗ | maxString = max; | |
| 116 | } | ||
| 117 | 54 | int newHeight = maxString; | |
| 118 | // make sure it's a multiple of 4 to keep things aligned in memory | ||
| 119 |
2/2✓ Branch 0 taken 23 times.
✓ Branch 1 taken 31 times.
|
54 | if (maxString == 0) { |
| 120 | 23 | return 0; | |
| 121 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 5 times.
|
31 | } else if (maxString <= 4) { |
| 122 | 26 | newHeight = 4; | |
| 123 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
|
5 | } else if (maxString <= 8) { |
| 124 | 2 | newHeight = 8; | |
| 125 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
|
3 | } else if (maxString <= 12) { |
| 126 | 1 | newHeight = 12; | |
| 127 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | } else if (maxString <= 16) { |
| 128 | ✗ | newHeight = 16; | |
| 129 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | } else if (maxString <= 20) { |
| 130 | ✗ | newHeight = 20; | |
| 131 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | } else if (maxString <= 24) { |
| 132 | ✗ | newHeight = 24; | |
| 133 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | } else if (maxString <= 28) { |
| 134 | ✗ | newHeight = 28; | |
| 135 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | } else if (maxString <= 32) { |
| 136 | ✗ | newHeight = 32; | |
| 137 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | } else if (maxString <= 36) { |
| 138 | ✗ | newHeight = 36; | |
| 139 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | } else if (maxString <= 40) { |
| 140 | ✗ | newHeight = 40; | |
| 141 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | } else if (maxString <= 44) { |
| 142 | ✗ | newHeight = 44; | |
| 143 | } else { | ||
| 144 | 2 | newHeight = 48; | |
| 145 | } | ||
| 146 | 31 | return newHeight; | |
| 147 | } | ||
| 148 | |||
| 149 | 50 | void BBB48StringOutput::createOutputLengths(FrameData& d, | |
| 150 | const std::string& pfx, | ||
| 151 | std::vector<std::string>& args) { | ||
| 152 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 29 times.
|
50 | if (d.gpioStringMap.empty()) { |
| 153 | 21 | return; | |
| 154 | } | ||
| 155 |
1/2✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
|
29 | std::ofstream outputFile; |
| 156 |
3/6✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 29 times.
✗ Branch 5 not taken.
|
29 | outputFile.open("/tmp/OutputLengths" + pfx + ".hp", |
| 157 | std::ofstream::out | std::ofstream::trunc); | ||
| 158 | |||
| 159 | 29 | std::map<int, std::vector<GPIOCommand>> sizes; | |
| 160 |
2/2✓ Branch 0 taken 132 times.
✓ Branch 1 taken 29 times.
|
161 | for (int x : d.gpioStringMap) { |
| 161 |
2/2✓ Branch 0 taken 64 times.
✓ Branch 1 taken 68 times.
|
132 | if (x != -1) { |
| 162 | 64 | int pc = m_strings[x]->m_outputBytes; | |
| 163 |
2/2✓ Branch 0 taken 60 times.
✓ Branch 1 taken 4 times.
|
64 | if (pc != 0) { |
| 164 |
2/2✓ Branch 0 taken 56 times.
✓ Branch 1 taken 60 times.
|
116 | for (auto& a : m_strings[x]->m_gpioCommands) { |
| 165 |
2/4✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
|
56 | sizes[a.channelOffset].push_back(a); |
| 166 | } | ||
| 167 | } | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | // Only offsets inside the frame get a check: the firmware turns every | ||
| 172 | // line off when the frame ends, so a command at or past the longest | ||
| 173 | // string never has anything to do. The last key is not necessarily the | ||
| 174 | // longest string's own end - a Falcon receiver chain has no end of | ||
| 175 | // string command, and a license clamped string keeps commands past its | ||
| 176 | // new length - so the chain is terminated explicitly rather than by | ||
| 177 | // looking one key past the end of the map. | ||
| 178 | 29 | std::vector<int> offsets; | |
| 179 |
2/2✓ Branch 0 taken 33 times.
✓ Branch 1 taken 29 times.
|
62 | for (auto& s : sizes) { |
| 180 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 28 times.
|
33 | if (s.first < d.maxStringLen) { |
| 181 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | offsets.push_back(s.first); |
| 182 | } | ||
| 183 | } | ||
| 184 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 29 times.
|
34 | for (size_t k = 0; k < offsets.size(); k++) { |
| 185 | 5 | int min = offsets[k]; | |
| 186 |
4/8✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
|
5 | outputFile << "\nCHECK_" << pfx << std::to_string(min) << ":\n"; |
| 187 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (min <= 255) { |
| 188 | 5 | outputFile << " QBNE skip_" << pfx << std::to_string(min) | |
| 189 |
6/12✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 5 times.
✗ Branch 11 not taken.
|
5 | << ", cur_data, " << std::to_string(min) << "\n"; |
| 190 | } else { | ||
| 191 | ✗ | if (min <= 0xFFFF) { | |
| 192 | ✗ | outputFile << " LDI r8, " << std::to_string(min) << "\n"; | |
| 193 | } else { | ||
| 194 | ✗ | outputFile << " LDI32 r8, " << std::to_string(min) << "\n"; | |
| 195 | } | ||
| 196 | ✗ | outputFile << " QBNE skip_" << pfx << std::to_string(min) | |
| 197 | ✗ | << ", cur_data, r8\n"; | |
| 198 | } | ||
| 199 | |||
| 200 |
3/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 5 times.
|
10 | for (auto& cmd : sizes[min]) { |
| 201 | 5 | int y = cmd.port; | |
| 202 |
1/2✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
|
11 | for (int x = 0; x < d.gpioStringMap.size(); x++) { |
| 203 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 6 times.
|
11 | if (d.gpioStringMap[x] == y) { |
| 204 | 5 | y = x; | |
| 205 | 5 | break; | |
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | 5 | std::string o = std::to_string(y + 1); | |
| 210 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (cmd.type) { |
| 211 | outputFile << " SET GPIO_MASK(o" << o << "_gpio), GPIO_MASK(o" | ||
| 212 | ✗ | << o << "_gpio), o" << o << "_pin\n"; | |
| 213 | } else { | ||
| 214 | outputFile << " CLR GPIO_MASK(o" << o << "_gpio), GPIO_MASK(o" | ||
| 215 |
7/14✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 5 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 5 times.
✗ Branch 13 not taken.
|
5 | << o << "_gpio), o" << o << "_pin\n"; |
| 216 | } | ||
| 217 | 5 | } | |
| 218 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | std::string next = "NO_PIXELS_CHECK"; |
| 219 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (k + 1 < offsets.size()) { |
| 220 | ✗ | next = "CHECK_" + pfx + std::to_string(offsets[k + 1]); | |
| 221 | } | ||
| 222 |
3/6✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
|
5 | outputFile << " LDI next_check, $CODE(" << next << ")\n"; |
| 223 |
4/8✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
|
5 | outputFile << "skip_" << pfx << std::to_string(min) << ":\n RET\n"; |
| 224 | 5 | } | |
| 225 | |||
| 226 |
2/2✓ Branch 0 taken 24 times.
✓ Branch 1 taken 5 times.
|
29 | if (offsets.empty()) { |
| 227 |
1/2✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
|
24 | args.emplace_back("-DFIRST_CHECK=NO_PIXELS_CHECK"); |
| 228 | } else { | ||
| 229 |
3/6✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
|
5 | args.push_back("-DFIRST_CHECK=CHECK_" + pfx + std::to_string(offsets[0])); |
| 230 | } | ||
| 231 |
1/2✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
|
29 | outputFile.close(); |
| 232 | 29 | } | |
| 233 | |||
| 234 | 27 | void BBB48StringOutput::groupFalconV4Chains() { | |
| 235 | // Group Falcon smart receiver ports into 4-port chains. Only the | ||
| 236 | // send-only V4 protocol is possible here: these capes have no listener | ||
| 237 | // hardware, so a V5 selection falls back to V4 handling. Done before | ||
| 238 | // the outputs are mapped so a chain whose first port has no pixels still | ||
| 239 | // gets a lane to carry its receiver's config packet. | ||
| 240 | 27 | int x = 0; | |
| 241 |
2/2✓ Branch 0 taken 157 times.
✓ Branch 1 taken 27 times.
|
184 | while (x < m_strings.size()) { |
| 242 | 157 | int p = x; | |
| 243 | 157 | PixelString* p1 = m_strings[x++]; | |
| 244 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 155 times.
|
157 | if (!p1->m_isSmartReceiver || |
| 245 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | (p1->smartReceiverType != PixelString::ReceiverType::FalconV4 && |
| 246 | ✗ | p1->smartReceiverType != PixelString::ReceiverType::FalconV5)) { | |
| 247 | 155 | continue; | |
| 248 | } | ||
| 249 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (p1->smartReceiverType == PixelString::ReceiverType::FalconV5) { |
| 250 | ✗ | LogWarn(VB_CHANNELOUT, "Falcon V5 (bidirectional) is not supported on BBB48String; using send-only V4 handling for output %d\n", p + 1); | |
| 251 | ✗ | p1->smartReceiverType = PixelString::ReceiverType::FalconV4; | |
| 252 | } | ||
| 253 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | PixelString* p2 = x < m_strings.size() ? m_strings[x++] : nullptr; |
| 254 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | PixelString* p3 = x < m_strings.size() ? m_strings[x++] : nullptr; |
| 255 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | PixelString* p4 = x < m_strings.size() ? m_strings[x++] : nullptr; |
| 256 | |||
| 257 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (p1->m_isInverted) { |
| 258 | ✗ | AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: Falcon smart receivers are not supported on inverted output " + std::to_string(p + 1)); | |
| 259 | ✗ | continue; | |
| 260 | } | ||
| 261 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (!falconV5Support) { |
| 262 |
3/8✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
2 | falconV5Support = new FalconV5Support(); |
| 263 | } | ||
| 264 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | falconV5Support->addReceiverChain(p1, p2, p3, p4, 0, 0, true); |
| 265 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | m_falconChainHeads.push_back(p); |
| 266 | // the four lines carry continuous pixel data - none of the v1/v2 | ||
| 267 | // style mid-frame gpio toggling | ||
| 268 | 2 | p1->m_gpioCommands.clear(); | |
| 269 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (p2) { |
| 270 | 2 | p2->m_gpioCommands.clear(); | |
| 271 | } | ||
| 272 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (p3) { |
| 273 | 2 | p3->m_gpioCommands.clear(); | |
| 274 | } | ||
| 275 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (p4) { |
| 276 | 2 | p4->m_gpioCommands.clear(); | |
| 277 | } | ||
| 278 | } | ||
| 279 | 27 | } | |
| 280 | |||
| 281 | 4 | void BBB48StringOutput::setupFalconV4Lanes(FrameData& d, const std::vector<std::array<uint8_t, 64>>& packets, | |
| 282 | Json::Value& root, std::vector<std::string>& args) { | ||
| 283 | // The chain heads this PRU drives get their packet bytes in their lane | ||
| 284 | // (bytes reversed: the firmware clocks MSB first, the wire wants the | ||
| 285 | // UART LSB first) and their pin in the FALCON_MASK_GPIOn the firmware | ||
| 286 | // toggles. Every other lane stays zero and its line stays low. | ||
| 287 | 4 | d.falconPacketLanes.clear(); | |
| 288 | 4 | uint32_t masks[4] = { 0, 0, 0, 0 }; | |
| 289 | 4 | int numStrings = d.gpioStringMap.size(); | |
| 290 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
8 | std::vector<uint8_t> lanes(57 * numStrings, 0); |
| 291 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 4 times.
|
20 | for (int lane = 0; lane < numStrings; lane++) { |
| 292 | 16 | int idx = d.gpioStringMap[lane]; | |
| 293 |
7/8✓ Branch 0 taken 10 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 14 times.
✓ Branch 7 taken 2 times.
|
16 | if (idx < 0 || std::find(m_falconChainHeads.begin(), m_falconChainHeads.end(), idx) == m_falconChainHeads.end()) { |
| 294 | 14 | continue; | |
| 295 | } | ||
| 296 |
5/10✓ 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.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
|
2 | const PinCapabilities& pin = PinCapabilities::getPinByName(root["outputs"][idx]["pin"].asString()); |
| 297 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | masks[pin.mappedGPIOIdx()] |= 1 << pin.mappedGPIO(); |
| 298 |
2/2✓ Branch 0 taken 114 times.
✓ Branch 1 taken 2 times.
|
116 | for (int b = 0; b < 57; b++) { |
| 299 | 114 | uint8_t v = packets[idx][b]; | |
| 300 | 114 | v = ((v * 0x0802LU & 0x22110LU) | (v * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16; | |
| 301 | 114 | lanes[b * numStrings + lane] = v; | |
| 302 | } | ||
| 303 | } | ||
| 304 |
6/8✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
|
4 | if (!masks[0] && !masks[1] && !masks[2] && !masks[3]) { |
| 305 | 2 | return; | |
| 306 | } | ||
| 307 | 2 | d.falconPacketLanes = std::move(lanes); | |
| 308 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | args.push_back("-DFALCONV4"); |
| 309 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
|
10 | for (int g = 0; g < 4; g++) { |
| 310 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
|
8 | if (masks[g]) { |
| 311 | char buf[40]; | ||
| 312 | 2 | snprintf(buf, sizeof(buf), "-DFALCON_MASK_GPIO%d=0x%08X", g, masks[g]); | |
| 313 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
4 | args.push_back(buf); |
| 314 | } | ||
| 315 | } | ||
| 316 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | } |
| 317 | |||
| 318 | 25 | void BBB48StringOutput::setupFalconV4Support(Json::Value& root, std::vector<std::string>& args1, std::vector<std::string>& args0) { | |
| 319 | // The packet content is static for a configuration, so it is generated | ||
| 320 | // once (after any license clamp has settled the pixel counts) and handed | ||
| 321 | // to whichever PRU drives each chain head: GPIO0 heads may be on the | ||
| 322 | // other PRU. | ||
| 323 |
5/6✓ Branch 0 taken 2 times.
✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 23 times.
✓ Branch 5 taken 2 times.
|
25 | if (!falconV5Support || falconV5Support->getReceiverChains().empty()) { |
| 324 | 23 | return; | |
| 325 | } | ||
| 326 | 2 | std::vector<std::array<uint8_t, 64>> packets; | |
| 327 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | packets.resize(m_strings.size()); |
| 328 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 2 times.
|
12 | for (auto& pk : packets) { |
| 329 | 10 | memset(&pk[0], 0, 64); | |
| 330 | } | ||
| 331 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | for (auto& rc : falconV5Support->getReceiverChains()) { |
| 332 | 2 | int port = rc->getPixelStrings()[0]->m_portNumber; | |
| 333 |
3/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
|
2 | if (port < 0 || port >= (int)packets.size()) { |
| 334 | ✗ | continue; // a port past the strings would write past the packets | |
| 335 | } | ||
| 336 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | rc->generateConfigPacket(&packets[port][0]); |
| 337 | } | ||
| 338 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | setupFalconV4Lanes(m_gpioData, packets, root, args1); |
| 339 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | setupFalconV4Lanes(m_gpio0Data, packets, root, args0); |
| 340 | 2 | } | |
| 341 | |||
| 342 | /* | ||
| 343 | * | ||
| 344 | */ | ||
| 345 | 28 | int BBB48StringOutput::Init(Json::Value config) { | |
| 346 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | LogDebug(VB_CHANNELOUT, "BBB48StringOutput::Init(JSON)\n"); |
| 347 | |||
| 348 |
2/4✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
|
28 | m_subType = config["subType"].asString(); |
| 349 |
2/4✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
|
28 | m_channelCount = config["channelCount"].asInt(); |
| 350 | 28 | m_gpio0Data.copyToPru = false; | |
| 351 | |||
| 352 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | std::string verPostf = ""; |
| 353 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | Json::Value root; |
| 354 |
2/4✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 28 times.
|
28 | if (!CapeUtils::INSTANCE.getStringConfig(m_subType, root)) { |
| 355 | // might have the version number on it | ||
| 356 | ✗ | if (config["pinoutVersion"].asString() == "2.x") { | |
| 357 | ✗ | verPostf = "-v2"; | |
| 358 | } | ||
| 359 | ✗ | if (config["pinoutVersion"].asString() == "3.x") { | |
| 360 | ✗ | verPostf = "-v3"; | |
| 361 | } | ||
| 362 | ✗ | if (!CapeUtils::INSTANCE.getStringConfig(m_subType + verPostf, root)) { | |
| 363 | ✗ | LogErr(VB_CHANNELOUT, "Could not read pin configuration for %s%s\n", | |
| 364 | m_subType.c_str(), verPostf.c_str()); | ||
| 365 | ✗ | AddOutputConfigWarning( | |
| 366 | ✗ | 13, "BBB48String: Could not read pin configuration for " + m_subType + | |
| 367 | ✗ | verPostf); | |
| 368 | ✗ | return 0; | |
| 369 | } | ||
| 370 | } | ||
| 371 | |||
| 372 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | m_licensedOutputs = CapeUtils::INSTANCE.getLicensedOutputs(); |
| 373 | |||
| 374 | // The mirror of the check in BBShiftString: this driver drives a header | ||
| 375 | // pin per output, so every output the cape declares has to name one. A | ||
| 376 | // shift-register cape's pinout gives a shift stage instead and its | ||
| 377 | // integer "pin" would come back through asString() as a pin name that | ||
| 378 | // matches nothing, leaving every port silently unconfigured. | ||
| 379 |
2/4✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
|
28 | const int capeOutputCount = root["outputs"].size(); |
| 380 |
2/2✓ Branch 0 taken 212 times.
✓ Branch 1 taken 27 times.
|
239 | for (int i = 0; i < capeOutputCount; i++) { |
| 381 |
6/10✓ Branch 0 taken 212 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 212 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 212 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 212 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 211 times.
✓ Branch 9 taken 1 times.
|
212 | if (!root["outputs"][i]["pin"].isIntegral()) { |
| 382 | 211 | continue; | |
| 383 | } | ||
| 384 |
3/6✓ 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.
|
1 | std::string capeDriver = root.get("driver", "BBShiftString").asString(); |
| 385 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogErr(VB_CHANNELOUT, "Cape %s is not a BBB48String cape - its string configuration gives shift register stages, so it needs the %s output type\n", |
| 386 | m_subType.c_str(), capeDriver.c_str()); | ||
| 387 |
4/8✓ 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.
|
2 | AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: " + m_subType + " needs the " + capeDriver + |
| 388 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | " output type - open the Pixel Strings page and save to correct it"); |
| 389 | 1 | return 0; | |
| 390 | 1 | } | |
| 391 | |||
| 392 |
2/4✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
|
27 | config["base"] = root; |
| 393 | |||
| 394 | 27 | int maxStringLen = 0; | |
| 395 |
4/6✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 163 times.
✓ Branch 5 taken 27 times.
|
190 | for (int i = 0; i < config["outputs"].size(); i++) { |
| 396 |
3/6✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 163 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 163 times.
✗ Branch 5 not taken.
|
163 | Json::Value s = config["outputs"][i]; |
| 397 |
3/8✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 163 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 163 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
163 | PixelString* newString = new PixelString(true); |
| 398 | |||
| 399 |
5/10✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 163 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 163 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 163 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 163 times.
|
163 | if (!newString->Init(s, &root["outputs"][i])) |
| 400 | ✗ | return 0; | |
| 401 | |||
| 402 | // This PRU drives the pins directly and has no per-port idle level, so | ||
| 403 | // an inverted protocol would put inverted data on a line that still | ||
| 404 | // idles low - worse than not supporting it. Undo the inversion and | ||
| 405 | // drive the port as a normal ws281x string. The preamble has to go | ||
| 406 | // with it: those bytes are meaningless to a ws281x part and would | ||
| 407 | // offset every pixel on the port. (Cape declared inversion never | ||
| 408 | // reaches here: no cape using this driver sets it.) | ||
| 409 | // The fill loop here emits one byte per channel, so a 16 bit part | ||
| 410 | // would come out at half length with the wrong bytes. Refuse it and | ||
| 411 | // drive the port 8 bit rather than produce garbage. | ||
| 412 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 163 times.
|
163 | if (newString->m_bytesPerChannel != 1) { |
| 413 | ✗ | LogWarn(VB_CHANNELOUT, "16 bit protocols are not supported on this cape; output %d will be driven as 8 bit\n", i + 1); | |
| 414 | ✗ | AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: 16 bit protocols are not supported on this cape, output " + std::to_string(i + 1)); | |
| 415 | ✗ | int pre = (int)newString->m_preamble.size(); | |
| 416 | ✗ | newString->m_outputBytes = (newString->m_outputBytes - pre) / newString->m_bytesPerChannel + pre; | |
| 417 | ✗ | newString->m_bytesPerChannel = 1; | |
| 418 | ✗ | for (auto& vs : newString->m_virtualStrings) { | |
| 419 | ✗ | vs.brightnessMap16 = nullptr; | |
| 420 | } | ||
| 421 | } | ||
| 422 | |||
| 423 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 163 times.
|
163 | if (newString->m_protocolInverted) { |
| 424 | ✗ | LogWarn(VB_CHANNELOUT, "Inverted protocols are not supported on this cape; output %d will be driven as ws2811\n", i + 1); | |
| 425 | ✗ | AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: inverted protocols are not supported on this cape, output " + std::to_string(i + 1)); | |
| 426 | ✗ | newString->invertOutput(); | |
| 427 | ✗ | newString->dropPreamble(); | |
| 428 | ✗ | newString->m_protocolInverted = false; | |
| 429 | } | ||
| 430 | |||
| 431 |
2/2✓ Branch 0 taken 29 times.
✓ Branch 1 taken 134 times.
|
163 | if (newString->m_outputBytes > maxStringLen) { |
| 432 | 29 | maxStringLen = newString->m_outputBytes; | |
| 433 | } | ||
| 434 | |||
| 435 |
1/2✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
|
163 | m_strings.push_back(newString); |
| 436 |
1/2✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
|
163 | } |
| 437 | |||
| 438 |
2/4✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
|
27 | int retVal = ChannelOutput::Init(config); |
| 439 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (retVal == 0) { |
| 440 | ✗ | return 0; | |
| 441 | } | ||
| 442 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (maxStringLen == 0) { |
| 443 | ✗ | LogErr(VB_CHANNELOUT, "No pixels configured in any string\n"); | |
| 444 | ✗ | return 1; | |
| 445 | } | ||
| 446 | |||
| 447 | 27 | std::vector<std::string> args; | |
| 448 | |||
| 449 | 27 | std::vector<std::string> split1args; | |
| 450 |
2/4✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
|
27 | split1args.push_back("-DRUNNING_ON_PRU" + std::to_string(BBB_PRU ? 1 : 0)); |
| 451 | 27 | std::vector<std::string> split0args; | |
| 452 |
2/4✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
|
27 | split0args.push_back("-DRUNNING_ON_PRU" + std::to_string(BBB_PRU ? 0 : 1)); |
| 453 | |||
| 454 |
2/4✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 27 times.
|
27 | if (config.isMember("pixelTiming")) { |
| 455 | ✗ | int pixelTiming = config["pixelTiming"].asInt(); | |
| 456 | ✗ | if (pixelTiming) { | |
| 457 | ✗ | args.push_back("-DPIXELTYPE_SLOW"); | |
| 458 | } | ||
| 459 | } | ||
| 460 | |||
| 461 |
1/2✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
|
27 | groupFalconV4Chains(); |
| 462 | |||
| 463 | 27 | std::vector<int> allStringMap; | |
| 464 | 27 | std::string outputList; | |
| 465 | 27 | int allMax = 0; | |
| 466 |
2/2✓ Branch 0 taken 163 times.
✓ Branch 1 taken 27 times.
|
190 | for (int x = 0; x < m_strings.size(); x++) { |
| 467 |
1/2✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
|
163 | bool chainHead = std::find(m_falconChainHeads.begin(), m_falconChainHeads.end(), x) != m_falconChainHeads.end(); |
| 468 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 163 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 163 times.
✗ Branch 5 not taken.
|
163 | if (m_strings[x]->m_outputBytes > 0 || chainHead) { |
| 469 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 162 times.
|
163 | if (x >= capeOutputCount) { |
| 470 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogErr(VB_CHANNELOUT, "Output %d is past the %d the %s cape declares\n", |
| 471 | x + 1, capeOutputCount, m_subType.c_str()); | ||
| 472 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: output " + std::to_string(x + 1) + |
| 473 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | " is past the end of the cape's pinout"); |
| 474 | 1 | continue; | |
| 475 | } | ||
| 476 | // need to output this pin, configure it | ||
| 477 |
4/8✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 162 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 162 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 162 times.
✗ Branch 7 not taken.
|
162 | std::string pinName = root["outputs"][x]["pin"].asString(); |
| 478 |
1/2✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
|
162 | const PinCapabilities& pin = PinCapabilities::getPinByName(pinName); |
| 479 |
1/2✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
|
162 | m_usedGPIOS.push_back(pinName); |
| 480 |
3/6✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 162 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 162 times.
✗ Branch 5 not taken.
|
324 | pin.configPin("gpio", true, "BBB48String-" + std::to_string(x + 1)); |
| 481 |
1/2✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
|
162 | allStringMap.push_back(x); |
| 482 |
5/6✓ Branch 0 taken 126 times.
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 126 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 126 times.
✓ Branch 5 taken 36 times.
|
162 | if (x >= m_licensedOutputs && m_strings[x]->m_outputBytes > 0) { |
| 483 | // apply limit | ||
| 484 | 126 | int pixels = 50; | |
| 485 | 126 | int chanCount = 0; | |
| 486 |
2/2✓ Branch 0 taken 126 times.
✓ Branch 1 taken 126 times.
|
252 | for (auto& a : m_strings[x]->m_virtualStrings) { |
| 487 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 126 times.
|
126 | if (pixels < a.pixelCount) { |
| 488 | ✗ | a.pixelCount = pixels; | |
| 489 | ✗ | if (outputList != "") { | |
| 490 | ✗ | outputList += ", "; | |
| 491 | } | ||
| 492 | ✗ | outputList += std::to_string(x + 1); | |
| 493 | } | ||
| 494 | 126 | pixels -= a.pixelCount; | |
| 495 |
1/2✓ Branch 0 taken 126 times.
✗ Branch 1 not taken.
|
126 | chanCount += a.pixelCount * a.channelsPerNode(); |
| 496 | } | ||
| 497 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 122 times.
|
126 | if (m_strings[x]->m_isSmartReceiver) { |
| 498 | 4 | chanCount = 0; | |
| 499 |
3/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
|
4 | if (outputList != "") { |
| 500 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | outputList += ", "; |
| 501 | } | ||
| 502 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | outputList += std::to_string(x + 1); |
| 503 | } | ||
| 504 |
1/2✓ Branch 0 taken 126 times.
✗ Branch 1 not taken.
|
126 | m_strings[x]->setPixelDataChannels(chanCount); |
| 505 | } | ||
| 506 | 162 | allMax = std::max(allMax, m_strings[x]->m_outputBytes); | |
| 507 |
3/4✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 157 times.
|
162 | if (pin.mappedGPIOIdx() == 0) { |
| 508 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | m_gpio0Data.gpioStringMap.push_back(x); |
| 509 | 5 | m_gpio0Data.maxStringLen = | |
| 510 | 5 | std::max(m_gpio0Data.maxStringLen, m_strings[x]->m_outputBytes); | |
| 511 | } else { | ||
| 512 |
1/2✓ Branch 0 taken 157 times.
✗ Branch 1 not taken.
|
157 | m_gpioData.gpioStringMap.push_back(x); |
| 513 | 157 | m_gpioData.maxStringLen = | |
| 514 | 157 | std::max(m_gpioData.maxStringLen, m_strings[x]->m_outputBytes); | |
| 515 | } | ||
| 516 | 162 | } | |
| 517 | } | ||
| 518 | |||
| 519 |
3/4✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 26 times.
|
27 | if (outputList != "") { |
| 520 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | std::string warning = "BBB48String is licensed for "; |
| 521 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | warning += std::to_string(m_licensedOutputs); |
| 522 | warning += " outputs, but one or more outputs is configured for more than " | ||
| 523 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | "50 pixels. Output(s): "; |
| 524 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | warning += outputList; |
| 525 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | AddOutputConfigWarning(UNKNOWN_WARNING_ID, warning); |
| 526 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogWarn(VB_CHANNELOUT, "WARNING: %s\n", warning.c_str()); |
| 527 | 1 | } | |
| 528 | |||
| 529 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 26 times.
|
27 | if (m_gpioData.gpioStringMap.empty()) { |
| 530 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | m_gpioData.gpioStringMap = m_gpio0Data.gpioStringMap; |
| 531 | 1 | m_gpioData.maxStringLen = m_gpio0Data.maxStringLen; | |
| 532 | 1 | m_gpio0Data.maxStringLen = 0; | |
| 533 | 1 | m_gpio0Data.gpioStringMap.clear(); | |
| 534 | } | ||
| 535 | 27 | bool hasSerial = true; | |
| 536 |
3/4✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 23 times.
|
27 | if (config.isMember("serialInUse")) { |
| 537 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | hasSerial = config["serialInUse"].asBool(); |
| 538 | } | ||
| 539 | |||
| 540 |
2/2✓ Branch 0 taken 23 times.
✓ Branch 1 taken 4 times.
|
27 | if (!m_gpio0Data.maxStringLen) { |
| 541 | // no GPIO0 output so no need for the second PRU to be used | ||
| 542 | 23 | hasSerial = true; | |
| 543 | } | ||
| 544 | 27 | bool canSplit = !hasSerial; | |
| 545 |
3/4✓ Branch 0 taken 23 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
|
27 | if (hasSerial && m_gpio0Data.maxStringLen && |
| 546 | ✗ | ((m_gpio0Data.maxStringLen + m_gpioData.maxStringLen) < 2000)) { | |
| 547 | // if there is plenty of time to output the GPIO0 stuff | ||
| 548 | // after the other GPIO's, let's do that | ||
| 549 | ✗ | args.push_back("-DSPLIT_GPIO0"); | |
| 550 | ✗ | args.push_back( | |
| 551 | ✗ | "-DGPIO0OUTPUTS=" + | |
| 552 | ✗ | std::to_string(mapSize(48, m_gpio0Data.gpioStringMap.size()))); | |
| 553 | |||
| 554 | ✗ | m_gpio0Data.gpioStringMap.insert(m_gpio0Data.gpioStringMap.end(), | |
| 555 | m_gpioData.gpioStringMap.begin(), | ||
| 556 | m_gpioData.gpioStringMap.end()); | ||
| 557 | ✗ | m_gpioData.gpioStringMap = m_gpio0Data.gpioStringMap; | |
| 558 | ✗ | m_gpio0Data.gpioStringMap.clear(); | |
| 559 | ✗ | m_gpioData.maxStringLen = | |
| 560 | ✗ | std::max(m_gpioData.maxStringLen, m_gpio0Data.maxStringLen); | |
| 561 | ✗ | m_gpio0Data.maxStringLen = 0; | |
| 562 | ✗ | canSplit = false; | |
| 563 | } | ||
| 564 |
2/2✓ Branch 0 taken 23 times.
✓ Branch 1 taken 4 times.
|
27 | if (!canSplit) { |
| 565 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | m_gpioData.gpioStringMap = allStringMap; |
| 566 | 23 | m_gpioData.maxStringLen = allMax; | |
| 567 | 23 | m_gpio0Data.gpioStringMap.clear(); | |
| 568 | } | ||
| 569 | |||
| 570 |
2/4✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
|
27 | int i = mapSize(root["outputs"].size(), m_gpioData.gpioStringMap.size()); |
| 571 |
2/2✓ Branch 0 taken 56 times.
✓ Branch 1 taken 27 times.
|
83 | while (m_gpioData.gpioStringMap.size() < i) { |
| 572 |
1/2✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
|
56 | m_gpioData.gpioStringMap.push_back(-1); |
| 573 | } | ||
| 574 |
2/4✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
|
27 | i = mapSize(root["outputs"].size(), m_gpio0Data.gpioStringMap.size()); |
| 575 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 27 times.
|
39 | while (m_gpio0Data.gpioStringMap.size() < i) { |
| 576 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | m_gpio0Data.gpioStringMap.push_back(-1); |
| 577 | } | ||
| 578 | // The firmware loads one byte per output into r10-r21; past 48 the load | ||
| 579 | // runs on into the GPIO bank address registers and every later GPIO | ||
| 580 | // store goes to an address made of pixel data. | ||
| 581 |
5/6✓ Branch 0 taken 25 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 25 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 25 times.
|
27 | if (m_gpioData.gpioStringMap.size() > 48 || m_gpio0Data.gpioStringMap.size() > 48) { |
| 582 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | LogErr(VB_CHANNELOUT, "%s uses %d outputs on one PRU, more than the 48 the firmware supports\n", |
| 583 | m_subType.c_str(), (int)std::max(m_gpioData.gpioStringMap.size(), m_gpio0Data.gpioStringMap.size())); | ||
| 584 |
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 | AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: " + m_subType + " uses more than 48 outputs on one PRU"); |
| 585 | 2 | return 0; | |
| 586 | } | ||
| 587 | |||
| 588 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | setupFalconV4Support(root, split1args, split0args); |
| 589 | |||
| 590 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | std::string v = "-DOUTPUTS="; |
| 591 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
25 | v += std::to_string(m_gpioData.gpioStringMap.size()); |
| 592 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | split1args.push_back(v); |
| 593 | |||
| 594 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | std::ofstream outputFile; |
| 595 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | outputFile.open("/tmp/PinConfiguration.hp", |
| 596 | std::ofstream::out | std::ofstream::trunc); | ||
| 597 |
2/2✓ Branch 0 taken 116 times.
✓ Branch 1 taken 25 times.
|
141 | for (int x = 0; x < m_gpioData.gpioStringMap.size(); x++) { |
| 598 | 116 | int idx = m_gpioData.gpioStringMap[x]; | |
| 599 |
2/2✓ Branch 0 taken 60 times.
✓ Branch 1 taken 56 times.
|
116 | if (idx >= 0) { |
| 600 | const PinCapabilities& pin = | ||
| 601 |
5/10✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 60 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 60 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 60 times.
✗ Branch 9 not taken.
|
60 | PinCapabilities::getPinByName(root["outputs"][idx]["pin"].asString()); |
| 602 | ✗ | outputFile << "#define o" << std::to_string(x + 1) << "_gpio " | |
| 603 |
6/12✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 60 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 60 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 60 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 60 times.
✗ Branch 11 not taken.
|
60 | << std::to_string(pin.mappedGPIOIdx()) << "\n"; |
| 604 | 60 | outputFile << "#define o" << std::to_string(x + 1) << "_pin " | |
| 605 |
6/12✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 60 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 60 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 60 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 60 times.
✗ Branch 11 not taken.
|
60 | << std::to_string(pin.mappedGPIO()) << "\n\n"; |
| 606 | } else { | ||
| 607 |
2/4✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
|
56 | split1args.push_back("-DNOOUT" + std::to_string(x + 1)); |
| 608 | } | ||
| 609 | } | ||
| 610 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | outputFile.close(); |
| 611 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
|
25 | if (m_gpio0Data.gpioStringMap.size() > 0) { |
| 612 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | split1args.push_back("-DDDRONLY"); |
| 613 | 4 | m_gpioData.copyToPru = false; | |
| 614 | 4 | m_gpio0Data.copyToPru = true; | |
| 615 | |||
| 616 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | std::string v = "-DOUTPUTS="; |
| 617 |
1/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
4 | v += std::to_string(m_gpio0Data.gpioStringMap.size()); |
| 618 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | split0args.push_back(v); |
| 619 | |||
| 620 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | outputFile.open("/tmp/PinConfigurationGPIO0.hp", |
| 621 | std::ofstream::out | std::ofstream::trunc); | ||
| 622 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 4 times.
|
20 | for (int x = 0; x < m_gpio0Data.gpioStringMap.size(); x++) { |
| 623 | 16 | int idx = m_gpio0Data.gpioStringMap[x]; | |
| 624 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 12 times.
|
16 | if (idx >= 0) { |
| 625 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | const PinCapabilities& pin = PinCapabilities::getPinByName( |
| 626 |
4/8✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
|
8 | root["outputs"][idx]["pin"].asString()); |
| 627 | ✗ | outputFile << "#define o" << std::to_string(x + 1) << "_gpio " | |
| 628 |
6/12✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
|
4 | << std::to_string(pin.mappedGPIOIdx()) << "\n"; |
| 629 | 4 | outputFile << "#define o" << std::to_string(x + 1) << "_pin " | |
| 630 |
6/12✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
|
4 | << std::to_string(pin.mappedGPIO()) << "\n\n"; |
| 631 | } else { | ||
| 632 |
2/4✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
|
12 | split0args.push_back("-DNOOUT" + std::to_string(x + 1)); |
| 633 | } | ||
| 634 | } | ||
| 635 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | outputFile.close(); |
| 636 | 4 | } | |
| 637 | |||
| 638 | #ifdef PRINT_STATS | ||
| 639 | args.push_back("-DRECORD_STATS"); | ||
| 640 | #endif | ||
| 641 | |||
| 642 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
50 | createOutputLengths(m_gpioData, "", split1args); |
| 643 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
50 | createOutputLengths(m_gpio0Data, "GPIO0_", split0args); |
| 644 | |||
| 645 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | compilePRUCode(args, split0args, split1args, !hasSerial); |
| 646 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 25 times.
|
25 | if (!StartPRU(!hasSerial)) { |
| 647 | ✗ | AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: Could not start PRU"); | |
| 648 | ✗ | return 0; | |
| 649 | } | ||
| 650 | |||
| 651 | // give each area two chunks (frame flipping) of DDR memory, from the | ||
| 652 | // shared region allocator so other outputs on the region (BBBMatrix | ||
| 653 | // panels, BBBSerial - e.g. the K8-B-Scroller runs all three) cannot | ||
| 654 | // overlap us | ||
| 655 | // each with room for the Falcon V4 config packet appended after the | ||
| 656 | // frame data | ||
| 657 | 28 | m_gpioData.frameSize = | |
| 658 | 25 | m_gpioData.gpioStringMap.size() * std::max(2400, m_gpioData.maxStringLen); | |
| 659 | 25 | m_gpioData.frameSize += m_gpioData.falconPacketLanes.size(); | |
| 660 | // leave a full memory page between to avoid conflicts | ||
| 661 | 25 | int offset = ((m_gpioData.frameSize / 4096) + 2) * 4096; | |
| 662 | 25 | m_gpio0Data.frameSize = m_gpio0Data.gpioStringMap.size() * | |
| 663 | 25 | std::max(2400, m_gpio0Data.maxStringLen); | |
| 664 | 25 | m_gpio0Data.frameSize += m_gpio0Data.falconPacketLanes.size(); | |
| 665 | 25 | int offset0 = ((m_gpio0Data.frameSize / 4096) + 2) * 4096; | |
| 666 | 25 | uint32_t ddrPhys = 0; | |
| 667 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | uint8_t* ddrBase = BBBPru::ddrAlloc("BBB48String", 2 * offset + 2 * offset0, ddrPhys); |
| 668 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
|
25 | if (!ddrBase) { |
| 669 | ✗ | LogErr(VB_CHANNELOUT, "BBB48String: no room in the PRU DDR region\n"); | |
| 670 | ✗ | AddOutputConfigWarning(20, "BBB48String: no room in the PRU DDR region - reduce string lengths or other outputs' buffers"); | |
| 671 | ✗ | return 0; | |
| 672 | } | ||
| 673 | 25 | m_gpioData.curData = ddrBase; | |
| 674 | 25 | m_gpioData.lastData = m_gpioData.curData + offset; | |
| 675 | |||
| 676 | 25 | m_gpio0Data.curData = m_gpioData.lastData + offset; | |
| 677 | 25 | m_gpio0Data.lastData = m_gpio0Data.curData + offset0; | |
| 678 | |||
| 679 | // flag the virtual strings whose channel map is a plain run so prepData | ||
| 680 | // can walk the channel data directly instead of through the map | ||
| 681 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | m_vsAffine.resize(m_strings.size()); |
| 682 |
2/2✓ Branch 0 taken 65 times.
✓ Branch 1 taken 25 times.
|
90 | for (size_t s = 0; s < m_strings.size(); s++) { |
| 683 |
1/2✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
|
65 | m_vsAffine[s].resize(m_strings[s]->m_virtualStrings.size()); |
| 684 |
2/2✓ Branch 0 taken 65 times.
✓ Branch 1 taken 65 times.
|
130 | for (size_t v = 0; v < m_strings[s]->m_virtualStrings.size(); v++) { |
| 685 | 65 | auto& vs = m_strings[s]->m_virtualStrings[v]; | |
| 686 | 65 | bool affine = true; | |
| 687 |
2/2✓ Branch 0 taken 1854 times.
✓ Branch 1 taken 65 times.
|
1919 | for (int i = 0; i + 3 < vs.chMapCount; i++) { |
| 688 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1854 times.
|
1854 | if (vs.chMap[i + 3] != vs.chMap[i] + 3) { |
| 689 | ✗ | affine = false; | |
| 690 | ✗ | break; | |
| 691 | } | ||
| 692 | } | ||
| 693 | 65 | m_vsAffine[s][v] = affine; | |
| 694 | } | ||
| 695 | } | ||
| 696 | |||
| 697 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | PixelString::AutoCreateOverlayModels(m_strings, m_autoCreatedModelNames); |
| 698 | 25 | return retVal; | |
| 699 | 28 | } | |
| 700 | |||
| 701 | 25 | int BBB48StringOutput::StartPRU(bool both) { | |
| 702 | 25 | m_curFrame = 0; | |
| 703 | 25 | int pruNumber = BBB_PRU; | |
| 704 | |||
| 705 | // The firmware is assembled per configuration just before this, so a | ||
| 706 | // missing program is a build failure (the assembler's errors are in the | ||
| 707 | // fppd output), not a PRU that needs a reboot. Like the others here it | ||
| 708 | // is a config warning: it comes down when the outputs are next loaded. | ||
| 709 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
50 | std::vector<std::string> programs = { "/tmp/FalconWS281x.out" }; |
| 710 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
|
25 | if (both) { |
| 711 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
8 | programs.push_back("/tmp/FalconWS281x_gpio0.out"); |
| 712 | } | ||
| 713 |
2/2✓ Branch 0 taken 29 times.
✓ Branch 1 taken 25 times.
|
54 | for (auto& fw : programs) { |
| 714 |
2/4✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 29 times.
|
29 | if (!FileExists(fw)) { |
| 715 | ✗ | LogErr(VB_CHANNELOUT, "BBB48String: PRU firmware %s failed to build\n", fw.c_str()); | |
| 716 | ✗ | AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: PRU firmware failed to build for this configuration"); | |
| 717 | ✗ | return 0; | |
| 718 | } | ||
| 719 | } | ||
| 720 | // run() is told not to clear PRU memory. The firmware zeroes its own | ||
| 721 | // control block (address, command, response) before it first reads the | ||
| 722 | // command, and reads nothing else until a non-zero command arrives. | ||
| 723 | // SendData() writes that command only after the frame's address, and on | ||
| 724 | // the first frame a full copy of its pixel data into the PRU memories, so | ||
| 725 | // a stale word left from an earlier run can neither start output nor be | ||
| 726 | // sent as pixel data. Skipping the clear also keeps this from touching | ||
| 727 | // the PRUSS while both cores are offline (crash e80ad9caf7). | ||
| 728 |
3/8✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 25 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
25 | m_pru = new BBBPru(pruNumber, true, true); |
| 729 | 25 | m_pruData = (BBB48StringData*)m_pru->data_ram; | |
| 730 |
3/6✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 25 times.
|
75 | if (!m_pru->run("/tmp/FalconWS281x.out", true, false)) { |
| 731 | ✗ | LogErr(VB_CHANNELOUT, | |
| 732 | "BBB48String: Unable to start PRU. May require a reboot.\n"); | ||
| 733 | ✗ | AddOutputConfigWarning(UNKNOWN_WARNING_ID, | |
| 734 | "BBB48String: Unable to start PRU. May require a reboot."); | ||
| 735 | ✗ | return 0; | |
| 736 | } | ||
| 737 | 25 | m_pruData->command = 0; | |
| 738 | 25 | m_pruData->address_dma = m_pru->ddr_addr; | |
| 739 | |||
| 740 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
|
25 | if (both) { |
| 741 |
3/8✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
4 | m_pru0 = new BBBPru(!pruNumber, true, true); |
| 742 | 4 | m_pru0Data = (BBB48StringData*)m_pru0->data_ram; | |
| 743 |
3/6✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
|
12 | if (!m_pru0->run("/tmp/FalconWS281x_gpio0.out", true, false)) { |
| 744 | ✗ | LogErr(VB_CHANNELOUT, | |
| 745 | "BBB48String: Unable to start PRU0. May require a reboot.\n"); | ||
| 746 | ✗ | AddOutputConfigWarning(UNKNOWN_WARNING_ID, | |
| 747 | "BBB48String: Unable to start PRU0. May require a reboot."); | ||
| 748 | ✗ | return 0; | |
| 749 | } | ||
| 750 | 4 | m_pru0Data->command = 0; | |
| 751 | 4 | m_pru0Data->address_dma = m_pru0->ddr_addr; | |
| 752 | } | ||
| 753 | |||
| 754 | 25 | return 1; | |
| 755 | 25 | } | |
| 756 | 25 | void BBB48StringOutput::StopPRU(bool wait) { | |
| 757 | // Send the stop command | ||
| 758 | 25 | m_pruData->response = 0; | |
| 759 | 25 | m_pruData->command = 0xFFFF; | |
| 760 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
|
25 | if (m_pru0Data) { |
| 761 | 4 | m_pru0Data->response = 0; | |
| 762 | 4 | m_pru0Data->command = 0xFFFF; | |
| 763 | } | ||
| 764 | 25 | __asm__ __volatile__("" ::: "memory"); | |
| 765 | |||
| 766 | // Wait for BOTH cores to acknowledge before stopping EITHER. Stopping a | ||
| 767 | // core through remoteproc can leave the PRUSS data RAM unreachable, and a | ||
| 768 | // read of it then takes an external abort (SIGBUS) rather than returning | ||
| 769 | // a value - which is what happened when PRU0's response was polled after | ||
| 770 | // PRU1 had already been stopped. So every access to PRU RAM here finishes | ||
| 771 | // while both cores are still up, and nothing touches it afterwards. | ||
| 772 | 25 | int cnt = 0; | |
| 773 |
6/8✓ Branch 0 taken 650 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 625 times.
✓ Branch 3 taken 25 times.
✓ Branch 4 taken 625 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 625 times.
✓ Branch 7 taken 25 times.
|
650 | while (wait && cnt < 25 && m_pruData->response != 0xFFFF) { |
| 774 |
1/2✓ Branch 0 taken 625 times.
✗ Branch 1 not taken.
|
625 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
| 775 | 625 | cnt++; | |
| 776 | 625 | __asm__ __volatile__("" ::: "memory"); | |
| 777 | } | ||
| 778 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
|
25 | if (m_pru0) { |
| 779 | 4 | cnt = 0; | |
| 780 |
6/8✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 100 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 100 times.
✓ Branch 7 taken 4 times.
|
104 | while (wait && cnt < 25 && m_pru0Data->response != 0xFFFF) { |
| 781 |
1/2✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
|
100 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
| 782 | 100 | cnt++; | |
| 783 | 100 | __asm__ __volatile__("" ::: "memory"); | |
| 784 | } | ||
| 785 | } | ||
| 786 | |||
| 787 | 25 | m_pru->stop(); | |
| 788 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | delete m_pru; |
| 789 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
|
25 | if (m_pru0) { |
| 790 | 4 | m_pru0->stop(); | |
| 791 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | delete m_pru0; |
| 792 | } | ||
| 793 | 25 | m_pru = NULL; | |
| 794 | 25 | m_pru0 = NULL; | |
| 795 | 25 | } | |
| 796 | /* | ||
| 797 | * | ||
| 798 | */ | ||
| 799 | 25 | int BBB48StringOutput::Close(void) { | |
| 800 | 25 | LogDebug(VB_CHANNELOUT, "BBB48StringOutput::Close()\n"); | |
| 801 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | BBBPru::ddrRelease("BBB48String"); |
| 802 |
2/2✓ Branch 0 taken 64 times.
✓ Branch 1 taken 25 times.
|
89 | for (auto& n : m_autoCreatedModelNames) { |
| 803 |
1/2✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
|
64 | PixelOverlayManager::INSTANCE.removeAutoOverlayModel(n); |
| 804 | } | ||
| 805 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
|
25 | if (!m_gpioData.gpioStringMap.empty() || !m_gpio0Data.gpioStringMap.empty()) { |
| 806 | 25 | StopPRU(); | |
| 807 | } | ||
| 808 | 25 | return ChannelOutput::Close(); | |
| 809 | } | ||
| 810 | |||
| 811 | 50 | void BBB48StringOutput::GetRequiredChannelRanges( | |
| 812 | const std::function<void(int, int)>& addRange) { | ||
| 813 | 50 | PixelString* ps = NULL; | |
| 814 |
2/2✓ Branch 0 taken 130 times.
✓ Branch 1 taken 50 times.
|
180 | for (int s = 0; s < m_strings.size(); s++) { |
| 815 | 130 | ps = m_strings[s]; | |
| 816 | 130 | int inCh = 0; | |
| 817 | 130 | int min = FPPD_MAX_CHANNELS; | |
| 818 | 130 | int max = -1; | |
| 819 |
2/2✓ Branch 0 taken 3810 times.
✓ Branch 1 taken 130 times.
|
3940 | for (int p = 0; p < ps->m_outputBytes; p++) { |
| 820 | 3810 | int ch = ps->m_outputMap[inCh++]; | |
| 821 |
1/2✓ Branch 0 taken 3810 times.
✗ Branch 1 not taken.
|
3810 | if (ch < FPPD_MAX_CHANNELS) { |
| 822 | 3810 | min = std::min(min, ch); | |
| 823 | 3810 | max = std::max(max, ch); | |
| 824 | } | ||
| 825 | } | ||
| 826 |
2/2✓ Branch 0 taken 122 times.
✓ Branch 1 taken 8 times.
|
130 | if (min < max) { |
| 827 |
1/2✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
|
122 | addRange(min, max); |
| 828 | } | ||
| 829 | } | ||
| 830 | 50 | } | |
| 831 | ✗ | void BBB48StringOutput::OverlayTestData(unsigned char* channelData, | |
| 832 | int cycleNum, float percentOfCycle, | ||
| 833 | int testType, | ||
| 834 | const Json::Value& config) { | ||
| 835 | ✗ | m_testCycle = cycleNum; | |
| 836 | ✗ | m_testType = testType; | |
| 837 | ✗ | m_testPercent = percentOfCycle; | |
| 838 | |||
| 839 | // We won't overlay the data here because we could have multiple strings | ||
| 840 | // pointing at the same channel range so a per-port test cannot | ||
| 841 | // be done via channel ranges. We'll record the test information and use | ||
| 842 | // that in prepData | ||
| 843 | ✗ | } | |
| 844 | |||
| 845 | // 8x8 byte transpose: in, rows are 8 consecutive bytes of 8 strings; out, | ||
| 846 | // rows are the 8 strings' bytes for 8 consecutive positions. (Same helper | ||
| 847 | // as BBShiftString.) | ||
| 848 | 54 | static inline void transpose8x8(uint8x8_t r[8]) { | |
| 849 | 54 | uint8x8x2_t t0 = vtrn_u8(r[0], r[1]); | |
| 850 | 54 | uint8x8x2_t t1 = vtrn_u8(r[2], r[3]); | |
| 851 | 54 | uint8x8x2_t t2 = vtrn_u8(r[4], r[5]); | |
| 852 | 54 | uint8x8x2_t t3 = vtrn_u8(r[6], r[7]); | |
| 853 | 162 | uint16x4x2_t s0 = vtrn_u16(vreinterpret_u16_u8(t0.val[0]), vreinterpret_u16_u8(t1.val[0])); | |
| 854 | 162 | uint16x4x2_t s1 = vtrn_u16(vreinterpret_u16_u8(t0.val[1]), vreinterpret_u16_u8(t1.val[1])); | |
| 855 | 162 | uint16x4x2_t s2 = vtrn_u16(vreinterpret_u16_u8(t2.val[0]), vreinterpret_u16_u8(t3.val[0])); | |
| 856 | 162 | uint16x4x2_t s3 = vtrn_u16(vreinterpret_u16_u8(t2.val[1]), vreinterpret_u16_u8(t3.val[1])); | |
| 857 | 162 | uint32x2x2_t u0 = vtrn_u32(vreinterpret_u32_u16(s0.val[0]), vreinterpret_u32_u16(s2.val[0])); | |
| 858 | 162 | uint32x2x2_t u1 = vtrn_u32(vreinterpret_u32_u16(s1.val[0]), vreinterpret_u32_u16(s3.val[0])); | |
| 859 | 162 | uint32x2x2_t u2 = vtrn_u32(vreinterpret_u32_u16(s0.val[1]), vreinterpret_u32_u16(s2.val[1])); | |
| 860 | 162 | uint32x2x2_t u3 = vtrn_u32(vreinterpret_u32_u16(s1.val[1]), vreinterpret_u32_u16(s3.val[1])); | |
| 861 | 54 | r[0] = vreinterpret_u8_u32(u0.val[0]); | |
| 862 | 54 | r[1] = vreinterpret_u8_u32(u1.val[0]); | |
| 863 | 54 | r[2] = vreinterpret_u8_u32(u2.val[0]); | |
| 864 | 54 | r[3] = vreinterpret_u8_u32(u3.val[0]); | |
| 865 | 54 | r[4] = vreinterpret_u8_u32(u0.val[1]); | |
| 866 | 54 | r[5] = vreinterpret_u8_u32(u1.val[1]); | |
| 867 | 54 | r[6] = vreinterpret_u8_u32(u2.val[1]); | |
| 868 | 54 | r[7] = vreinterpret_u8_u32(u3.val[1]); | |
| 869 | 54 | } | |
| 870 | |||
| 871 | 1024 | void BBB48StringOutput::prepData(FrameData& d, unsigned char* channelData) { | |
| 872 | 1024 | PixelStringTester* tester = nullptr; | |
| 873 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1024 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1024 | if (m_testType && m_testCycle >= 0) { |
| 874 | ✗ | tester = PixelStringTester::getPixelStringTester(m_testType); | |
| 875 | // null for a test type no string tester implements; the strings then | ||
| 876 | // just send their normal data | ||
| 877 | ✗ | if (tester) { | |
| 878 | ✗ | tester->prepareTestData(m_testCycle, m_testPercent); | |
| 879 | } | ||
| 880 | } | ||
| 881 | 1024 | const int numStrings = d.gpioStringMap.size(); | |
| 882 | // per string slot: either a fully prepared buffer (test mode) or the | ||
| 883 | // PixelString whose virtual strings are rendered on the fly per tile, | ||
| 884 | // with a cursor tracking where in the virtual string list the next tile | ||
| 885 | // continues. (Same scheme as BBShiftString::prepData.) | ||
| 886 | struct SlotSrc { | ||
| 887 | const uint8_t* buf = nullptr; | ||
| 888 | PixelString* ps = nullptr; | ||
| 889 | const uint8_t* affine = nullptr; | ||
| 890 | uint32_t len = 0; | ||
| 891 | uint32_t vsIdx = 0; | ||
| 892 | uint32_t vsOff = 0; | ||
| 893 | }; | ||
| 894 |
1/2✓ Branch 0 taken 1024 times.
✗ Branch 1 not taken.
|
2048 | std::vector<SlotSrc> slots(numStrings); |
| 895 | 1024 | uint32_t newMaxLen = 0; | |
| 896 |
2/2✓ Branch 0 taken 2240 times.
✓ Branch 1 taken 1024 times.
|
3264 | for (int s = 0; s < numStrings; s++) { |
| 897 | 2240 | int idx = d.gpioStringMap[s]; | |
| 898 |
2/2✓ Branch 0 taken 1120 times.
✓ Branch 1 taken 1120 times.
|
2240 | if (idx >= 0) { |
| 899 | 1120 | PixelString* ps = m_strings[idx]; | |
| 900 | 1120 | uint32_t newLen = ps->m_outputBytes; | |
| 901 | 1120 | SlotSrc& sl = slots[s]; | |
| 902 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1120 times.
|
1120 | if (tester) { |
| 903 | ✗ | sl.buf = tester->createTestData(ps, m_testCycle, m_testPercent, | |
| 904 | channelData, newLen); | ||
| 905 | } else { | ||
| 906 | 1120 | sl.ps = ps; | |
| 907 | 1120 | sl.affine = m_vsAffine[idx].data(); | |
| 908 | } | ||
| 909 | 1120 | sl.len = newLen; | |
| 910 | 1120 | newMaxLen = std::max(newLen, newMaxLen); | |
| 911 | } | ||
| 912 | } | ||
| 913 | 1024 | d.outputStringLen = newMaxLen; | |
| 914 | |||
| 915 | // Interleave the string data in tiles that stay in the L1 cache instead | ||
| 916 | // of writing a byte every numStrings bytes across the whole frame, with | ||
| 917 | // the brightness/gamma application fused into the tile fill. Virtual | ||
| 918 | // strings whose channel map is a simple run skip the per-channel map | ||
| 919 | // indirection. Unmapped slots and the tail of strings shorter than the | ||
| 920 | // longest now output zeros instead of stale buffer content. | ||
| 921 | 1024 | uint8_t* out = d.curData; | |
| 922 | 1024 | constexpr uint32_t TILE = 64; | |
| 923 | uint8_t col[8][TILE]; | ||
| 924 |
2/2✓ Branch 0 taken 536 times.
✓ Branch 1 taken 1024 times.
|
1560 | for (uint32_t p0 = 0; p0 < newMaxLen; p0 += TILE) { |
| 925 | 536 | const uint32_t n = std::min(TILE, newMaxLen - p0); | |
| 926 | 536 | const uint32_t nFull = n & ~7; | |
| 927 |
2/2✓ Branch 0 taken 542 times.
✓ Branch 1 taken 536 times.
|
1078 | for (int s0 = 0; s0 < numStrings; s0 += 8) { |
| 928 | 542 | const int ns = std::min(8, numStrings - s0); | |
| 929 |
2/2✓ Branch 0 taken 2240 times.
✓ Branch 1 taken 542 times.
|
2782 | for (int x = 0; x < ns; ++x) { |
| 930 | 2240 | SlotSrc& sl = slots[s0 + x]; | |
| 931 |
2/2✓ Branch 0 taken 1096 times.
✓ Branch 1 taken 1144 times.
|
2240 | uint32_t avail = sl.len > p0 ? std::min(n, sl.len - p0) : 0; |
| 932 | 2240 | uint32_t p = 0; | |
| 933 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2240 times.
|
2240 | if (sl.buf) { |
| 934 | ✗ | memcpy(col[x], sl.buf + p0, avail); | |
| 935 | ✗ | p = avail; | |
| 936 |
2/2✓ Branch 0 taken 1120 times.
✓ Branch 1 taken 1120 times.
|
2240 | } else if (sl.ps) { |
| 937 | 1120 | auto& vstrings = sl.ps->m_virtualStrings; | |
| 938 |
5/6✓ Branch 0 taken 1096 times.
✓ Branch 1 taken 1120 times.
✓ Branch 2 taken 1096 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1096 times.
✓ Branch 5 taken 1120 times.
|
2216 | while (p < avail && sl.vsIdx < vstrings.size()) { |
| 939 | 1096 | auto& vs = vstrings[sl.vsIdx]; | |
| 940 | 1096 | uint32_t m = std::min(avail - p, (uint32_t)vs.chMapCount - sl.vsOff); | |
| 941 | 1096 | const uint8_t* br = vs.brightnessMap; | |
| 942 | 1096 | const int* mp = vs.chMap; | |
| 943 |
1/2✓ Branch 0 taken 1096 times.
✗ Branch 1 not taken.
|
1096 | if (sl.affine[sl.vsIdx]) { |
| 944 | 1096 | uint32_t c = sl.vsOff % 3; | |
| 945 | 1096 | int32_t base = (int32_t)(sl.vsOff - c); | |
| 946 |
2/2✓ Branch 0 taken 36900 times.
✓ Branch 1 taken 1096 times.
|
37996 | for (uint32_t k = 0; k < m; ++k) { |
| 947 | 36900 | col[x][p++] = br[channelData[mp[c] + base]]; | |
| 948 |
2/2✓ Branch 0 taken 12300 times.
✓ Branch 1 taken 24600 times.
|
36900 | if (++c == 3) { |
| 949 | 12300 | c = 0; | |
| 950 | 12300 | base += 3; | |
| 951 | } | ||
| 952 | } | ||
| 953 | } else { | ||
| 954 | ✗ | const int* mo = mp + sl.vsOff; | |
| 955 | ✗ | for (uint32_t k = 0; k < m; ++k) { | |
| 956 | ✗ | col[x][p++] = br[channelData[mo[k]]]; | |
| 957 | } | ||
| 958 | } | ||
| 959 | 1096 | sl.vsOff += m; | |
| 960 |
1/2✓ Branch 0 taken 1096 times.
✗ Branch 1 not taken.
|
1096 | if (sl.vsOff >= (uint32_t)vs.chMapCount) { |
| 961 | 1096 | sl.vsOff = 0; | |
| 962 | 1096 | sl.vsIdx++; | |
| 963 | } | ||
| 964 | } | ||
| 965 | } | ||
| 966 |
2/2✓ Branch 0 taken 1290 times.
✓ Branch 1 taken 950 times.
|
2240 | if (p < n) { |
| 967 | 1290 | memset(&col[x][p], 0, n - p); | |
| 968 | } | ||
| 969 | } | ||
| 970 | 542 | uint32_t g = 0; | |
| 971 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 524 times.
|
542 | if (ns == 8) { |
| 972 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 18 times.
|
72 | for (; g < nFull; g += 8) { |
| 973 | uint8x8_t r[8]; | ||
| 974 |
2/2✓ Branch 0 taken 432 times.
✓ Branch 1 taken 54 times.
|
486 | for (int x = 0; x < 8; ++x) { |
| 975 |
1/2✓ Branch 0 taken 432 times.
✗ Branch 1 not taken.
|
864 | r[x] = vld1_u8(&col[x][g]); |
| 976 | } | ||
| 977 | 54 | transpose8x8(r); | |
| 978 |
2/2✓ Branch 0 taken 432 times.
✓ Branch 1 taken 54 times.
|
486 | for (int i = 0; i < 8; i++) { |
| 979 |
1/2✓ Branch 0 taken 432 times.
✗ Branch 1 not taken.
|
432 | vst1_u8(&out[(size_t)(p0 + g + i) * numStrings + s0], r[i]); |
| 980 | } | ||
| 981 | } | ||
| 982 | } | ||
| 983 |
2/2✓ Branch 0 taken 19902 times.
✓ Branch 1 taken 542 times.
|
20444 | for (uint32_t p = g; p < n; ++p) { |
| 984 |
2/2✓ Branch 0 taken 79896 times.
✓ Branch 1 taken 19902 times.
|
99798 | for (int x = 0; x < ns; ++x) { |
| 985 | 79896 | out[(size_t)(p0 + p) * numStrings + s0 + x] = col[x][p]; | |
| 986 | } | ||
| 987 | } | ||
| 988 | } | ||
| 989 | } | ||
| 990 | 1024 | } | |
| 991 | |||
| 992 | /* | ||
| 993 | * | ||
| 994 | */ | ||
| 995 | 512 | void BBB48StringOutput::PrepData(unsigned char* channelData) { | |
| 996 | 512 | LogExcess(VB_CHANNELOUT, "BBB48StringOutput::PrepData(%p)\n", channelData); | |
| 997 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 512 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 512 times.
|
512 | if (m_gpioData.gpioStringMap.empty() && m_gpio0Data.gpioStringMap.empty()) { |
| 998 | ✗ | return; | |
| 999 | } | ||
| 1000 | |||
| 1001 | 512 | m_curFrame++; | |
| 1002 | |||
| 1003 | #ifdef PRINT_STATS | ||
| 1004 | int max = 0; | ||
| 1005 | for (int x = 0; x < MAX_WS2811_TIMINGS; x++) { | ||
| 1006 | if (max < m_pruData->timings[x]) { | ||
| 1007 | max = m_pruData->timings[x]; | ||
| 1008 | } | ||
| 1009 | } | ||
| 1010 | if (max > 300 || (m_curFrame % 10) == 1) { | ||
| 1011 | for (int x = 0; x < MAX_WS2811_TIMINGS;) { | ||
| 1012 | printf("%8X ", m_pruData->timings[x]); | ||
| 1013 | ++x; | ||
| 1014 | if ((x) % 16 == 0) { | ||
| 1015 | printf("\n"); | ||
| 1016 | } | ||
| 1017 | } | ||
| 1018 | printf("\n%d: max %d\n", m_curFrame, max); | ||
| 1019 | } | ||
| 1020 | #endif | ||
| 1021 | 512 | prepData(m_gpioData, channelData); | |
| 1022 | 512 | prepData(m_gpio0Data, channelData); | |
| 1023 | 512 | m_testCycle = -1; | |
| 1024 | |||
| 1025 |
6/6✓ Branch 0 taken 12 times.
✓ Branch 1 taken 500 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 510 times.
|
512 | if (!m_falconChainHeads.empty() && (m_curFrame % 12) == 1) { |
| 1026 | // append the config packet after the frame data; the firmware sends | ||
| 1027 | // it when the command has the packet bit set | ||
| 1028 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
|
6 | for (FrameData* d : { &m_gpioData, &m_gpio0Data }) { |
| 1029 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | if (!d->falconPacketLanes.empty()) { |
| 1030 | 4 | memcpy(d->curData + d->outputStringLen * d->gpioStringMap.size(), | |
| 1031 | 2 | d->falconPacketLanes.data(), d->falconPacketLanes.size()); | |
| 1032 | } | ||
| 1033 | } | ||
| 1034 | 2 | m_sendFalconPacket = true; | |
| 1035 | } | ||
| 1036 | } | ||
| 1037 | |||
| 1038 | 486 | void BBB48StringOutput::sendData(FrameData& d, uint32_t* dptr) { | |
| 1039 | 486 | bool doSwap = false; | |
| 1040 | // compare everything the copy below would place in PRU memory - the | ||
| 1041 | // firmware reads those bytes from there rather than DDR, so a change | ||
| 1042 | // past the compared range would leave it sending the old data | ||
| 1043 | 486 | uint32_t pruCopySize = std::min(d.frameSize, (uint32_t)(7624 + 7624 + m_pru->shared_ram_size)); | |
| 1044 |
2/2✓ Branch 0 taken 470 times.
✓ Branch 1 taken 16 times.
|
486 | if (d.copyToPru && |
| 1045 |
4/4✓ Branch 0 taken 449 times.
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 423 times.
|
470 | (m_curFrame == 1 || memcmp(d.lastData, d.curData, pruCopySize))) { |
| 1046 | // don't copy to PRU memory unless really needed to avoid bus contention | ||
| 1047 | |||
| 1048 | // copy first 7.5K into PRU mem directly | ||
| 1049 | 47 | int fullsize = d.frameSize; | |
| 1050 | 47 | int mx = d.frameSize; | |
| 1051 |
1/2✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
|
47 | if (mx > (8 * 1024 - 512)) { |
| 1052 | 47 | mx = 8 * 1024 - 512; | |
| 1053 | } | ||
| 1054 | |||
| 1055 | // first 7.5K to main PRU ram | ||
| 1056 | 47 | m_pru->memcpyToPRU(m_pru->data_ram + 512, d.curData, mx); | |
| 1057 | 47 | fullsize -= 7624; | |
| 1058 |
1/2✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
|
47 | if (fullsize > 0) { |
| 1059 | 47 | int outsize = fullsize; | |
| 1060 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 38 times.
|
47 | if (outsize > (8 * 1024 - 512)) { |
| 1061 | 9 | outsize = 8 * 1024 - 512; | |
| 1062 | } | ||
| 1063 | // second 7.5K to other PRU ram | ||
| 1064 | 47 | m_pru->memcpyToPRU(m_pru->other_data_ram + 512, d.curData + 7624, | |
| 1065 | outsize); | ||
| 1066 | 47 | fullsize -= 7624; | |
| 1067 | } | ||
| 1068 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 38 times.
|
47 | if (fullsize > 0) { |
| 1069 | 9 | int outsize = fullsize; | |
| 1070 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
|
9 | if (outsize > m_pru->shared_ram_size) { |
| 1071 | 1 | outsize = m_pru->shared_ram_size; | |
| 1072 | } | ||
| 1073 | 9 | m_pru->memcpyToPRU(m_pru->shared_ram, d.curData + 7624 + 7624, outsize); | |
| 1074 | } | ||
| 1075 | } | ||
| 1076 | |||
| 1077 | // Map | ||
| 1078 | 486 | size_t offset = d.curData - m_pru->ddr; | |
| 1079 | 486 | *dptr = (m_pru->ddr_addr + offset); | |
| 1080 | 486 | std::swap(d.lastData, d.curData); | |
| 1081 | 486 | } | |
| 1082 | |||
| 1083 | 470 | int BBB48StringOutput::SendData(unsigned char* channelData) { | |
| 1084 | 470 | LogExcess(VB_CHANNELOUT, "BBB48StringOutput::SendData(%p)\n", channelData); | |
| 1085 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 470 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 470 times.
|
470 | if (m_gpioData.gpioStringMap.empty() && m_gpio0Data.gpioStringMap.empty()) { |
| 1086 | ✗ | return 0; | |
| 1087 | } | ||
| 1088 | |||
| 1089 | 470 | sendData(m_gpioData, &m_pruData->address_dma); | |
| 1090 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 454 times.
|
470 | if (m_pru0Data) { |
| 1091 | 16 | sendData(m_gpio0Data, &m_pru0Data->address_dma); | |
| 1092 | } | ||
| 1093 | |||
| 1094 | // make sure memory is flushed before command is set to 1 | ||
| 1095 | 470 | __asm__ __volatile__("" ::: "memory"); | |
| 1096 | |||
| 1097 | // Send the start command | ||
| 1098 | 470 | uint32_t flag = 0; | |
| 1099 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 470 times.
|
470 | if (m_testType == 999) { |
| 1100 | // pixel counting needs the GPIO commands and such turned off | ||
| 1101 | ✗ | flag = 0x8000; | |
| 1102 | } | ||
| 1103 | // bit 16: a Falcon V4 config packet follows the frame data, for the | ||
| 1104 | // PRUs that drive a chain head | ||
| 1105 | 470 | uint32_t flag0 = flag; | |
| 1106 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 468 times.
|
470 | if (m_sendFalconPacket) { |
| 1107 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (!m_gpioData.falconPacketLanes.empty()) { |
| 1108 | 2 | flag |= 0x10000; | |
| 1109 | } | ||
| 1110 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!m_gpio0Data.falconPacketLanes.empty()) { |
| 1111 | ✗ | flag0 |= 0x10000; | |
| 1112 | } | ||
| 1113 | 2 | m_sendFalconPacket = false; | |
| 1114 | } | ||
| 1115 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 454 times.
|
470 | if (m_pru0Data) { |
| 1116 | 16 | m_pru0Data->command = m_gpio0Data.outputStringLen | flag0; | |
| 1117 | } | ||
| 1118 | 470 | m_pruData->command = m_gpioData.outputStringLen | flag; | |
| 1119 | |||
| 1120 | 470 | return m_channelCount; | |
| 1121 | } | ||
| 1122 | |||
| 1123 | /* | ||
| 1124 | * | ||
| 1125 | */ | ||
| 1126 | 27 | void BBB48StringOutput::DumpConfig(void) { | |
| 1127 | 27 | LogDebug(VB_CHANNELOUT, "BBB48StringOutput::DumpConfig()\n"); | |
| 1128 | 27 | LogDebug(VB_CHANNELOUT, " type : %s\n", m_subType.c_str()); | |
| 1129 | 27 | LogDebug(VB_CHANNELOUT, " strings : %d\n", m_strings.size()); | |
| 1130 | 27 | LogDebug(VB_CHANNELOUT, " longest string: %d channels\n", | |
| 1131 | std::max(m_gpio0Data.maxStringLen, m_gpioData.maxStringLen)); | ||
| 1132 | |||
| 1133 |
2/2✓ Branch 0 taken 163 times.
✓ Branch 1 taken 27 times.
|
190 | for (int i = 0; i < m_strings.size(); i++) { |
| 1134 | 163 | LogDebug(VB_CHANNELOUT, " string #%02d\n", i); | |
| 1135 | 163 | m_strings[i]->DumpConfig(); | |
| 1136 | } | ||
| 1137 | |||
| 1138 | 27 | ChannelOutput::DumpConfig(); | |
| 1139 | 27 | } | |
| 1140 |