xref: /openbmc/phosphor-bmc-code-mgmt/i2c-vr/isl69269/isl69269.cpp (revision c1b36628298d7799677680e70d455f85acf83650)
1 #include "isl69269.hpp"
2 
3 #include "common/include/i2c/i2c.hpp"
4 
5 #include <phosphor-logging/lg2.hpp>
6 
7 #include <string>
8 
9 PHOSPHOR_LOG2_USING;
10 
11 namespace phosphor::software::VR
12 {
13 
14 constexpr uint8_t regProgStatus = 0x7E;
15 constexpr uint8_t regHexModeCFG0 = 0x87;
16 constexpr uint8_t regCRC = 0x94;
17 constexpr uint8_t regHexModeCFG1 = 0xBD;
18 constexpr uint8_t regDMAData = 0xC5;
19 constexpr uint8_t regDMAAddr = 0xC7;
20 constexpr uint8_t regRestoreCfg = 0xF2;
21 
22 constexpr uint8_t regRemainginWrites = 0x35;
23 
24 constexpr uint8_t gen3SWRevMin = 0x06;
25 constexpr uint8_t deviceIdLength = 4;
26 
27 constexpr uint8_t gen3Legacy = 1;
28 constexpr uint8_t gen3Production = 2;
29 constexpr uint8_t gen2Hex = 3;
30 constexpr uint8_t gen3p5 = 4;
31 
32 constexpr uint16_t cfgId = 7;
33 constexpr uint16_t gen3FileHead = 5;
34 constexpr uint16_t gen3LegacyCRC = 276 - gen3FileHead;
35 constexpr uint16_t gen3ProductionCRC = 290 - gen3FileHead;
36 constexpr uint8_t checksumLen = 4;
37 constexpr uint8_t deviceRevisionLen = 4;
38 
39 // Common pmBus Command codes
40 constexpr uint8_t pmBusDeviceId = 0xAD;
41 constexpr uint8_t pmBusDeviceRev = 0xAE;
42 
43 // Config file constants
44 constexpr char recordTypeData = 0x00;
45 constexpr char recordTypeHeader = 0x49;
46 
47 constexpr uint8_t defaultBufferSize = 16;
48 constexpr uint8_t programBufferSize = 32;
49 
50 constexpr uint8_t zeroByteLen = 0;
51 constexpr uint8_t oneByteLen = 1;
52 constexpr uint8_t threeByteLen = 3;
53 constexpr uint8_t fourByteLen = 4;
54 
55 // RAA Gen2
56 constexpr uint8_t gen2RegProgStatus = 0x07;
57 constexpr uint8_t gen2RegCRC = 0x3F;
58 constexpr uint8_t gen2RegRemainginWrites = 0xC2;
59 
60 constexpr uint32_t gen2RevMin = 0x02000003;
61 
62 constexpr uint8_t hexFileRev = 0x00;
63 constexpr uint16_t gen2FileHead = 6;
64 constexpr uint16_t gen2CRC = 600 - gen2FileHead;
65 
66 // RAA Gen3p5
67 constexpr uint8_t regGen3p5ProgStatus = 0x83;
68 constexpr uint8_t gen3p5RegCRC = 0xF8;
69 constexpr uint8_t gen3p5HWRevMax = 0x06;
70 constexpr uint8_t gen3p5HWRevMin = 0x03;
71 constexpr uint16_t gen3p5cfgId = 3;
72 constexpr uint16_t gen3p5FileHead = 5;
73 constexpr uint16_t gen3p5CRC = 336 - gen3p5FileHead;
74 
75 ISL69269::ISL69269(sdbusplus::async::context& ctx, uint16_t bus,
76                    uint16_t address, Gen gen) :
77     VoltageRegulator(ctx), i2cInterface(phosphor::i2c::I2C(bus, address)),
78     generation(gen)
79 {}
80 
81 inline void shiftLeftFromLSB(const uint8_t* data, uint32_t* result)
82 {
83     *result = (static_cast<uint32_t>(data[3]) << 24) |
84               (static_cast<uint32_t>(data[2]) << 16) |
85               (static_cast<uint32_t>(data[1]) << 8) |
86               (static_cast<uint32_t>(data[0]));
87 }
88 
89 inline void shiftLeftFromMSB(const uint8_t* data, uint32_t* result)
90 {
91     *result = (static_cast<uint32_t>(data[0]) << 24) |
92               (static_cast<uint32_t>(data[1]) << 16) |
93               (static_cast<uint32_t>(data[2]) << 8) |
94               (static_cast<uint32_t>(data[3]));
95 }
96 
97 static uint8_t calcCRC8(const uint8_t* data, uint8_t len)
98 {
99     uint8_t crc = 0x00;
100     int i = 0;
101     int b = 0;
102 
103     for (i = 0; i < len; i++)
104     {
105         crc ^= data[i];
106         for (b = 0; b < 8; b++)
107         {
108             if (crc & 0x80)
109             {
110                 crc = (crc << 1) ^ 0x07; // polynomial 0x07
111             }
112             else
113             {
114                 crc = (crc << 1);
115             }
116         }
117     }
118 
119     return crc;
120 }
121 
122 sdbusplus::async::task<bool> ISL69269::dmaReadWrite(uint8_t* reg, uint8_t* resp)
123 {
124     if (reg == nullptr || resp == nullptr)
125     {
126         error("dmaReadWrite invalid input");
127         co_return false;
128     }
129 
130     uint8_t tbuf[defaultBufferSize] = {0};
131     uint8_t tlen = threeByteLen;
132     uint8_t rbuf[defaultBufferSize] = {0};
133     uint8_t rlen = zeroByteLen;
134 
135     tbuf[0] = regDMAAddr;
136     std::memcpy(&tbuf[1], reg, 2);
137 
138     // NOLINTBEGIN(clang-analyzer-core.uninitialized.Branch)
139     if (!(co_await i2cInterface.sendReceive(tbuf, tlen, rbuf, rlen)))
140     // NOLINTEND(clang-analyzer-core.uninitialized.Branch)
141     {
142         error("dmaReadWrite failed with {CMD}", "CMD",
143               std::string("_REG_DMA_ADDR"));
144         co_return false;
145     }
146 
147     tlen = oneByteLen;
148     rlen = fourByteLen;
149 
150     tbuf[0] = regDMAData;
151     if (!(co_await i2cInterface.sendReceive(tbuf, tlen, resp, rlen)))
152     {
153         error("dmaReadWrite failed with {CMD}", "CMD",
154               std::string("_REG_DMA_DATA"));
155         co_return false;
156     }
157 
158     co_return true;
159 }
160 
161 sdbusplus::async::task<bool> ISL69269::getRemainingWrites(uint8_t* remain)
162 {
163     uint8_t tbuf[defaultBufferSize] = {0};
164     uint8_t rbuf[defaultBufferSize] = {0};
165 
166     tbuf[0] =
167         (generation == Gen::Gen2) ? gen2RegRemainginWrites : regRemainginWrites;
168     tbuf[1] = 0x00;
169     if (!(co_await dmaReadWrite(tbuf, rbuf)))
170     {
171         error("getRemainingWrites failed");
172         co_return false;
173     }
174 
175     *remain = rbuf[0];
176     co_return true;
177 }
178 
179 sdbusplus::async::task<bool> ISL69269::getHexMode(uint8_t* mode)
180 {
181     if (generation == Gen::Gen2)
182     {
183         *mode = gen2Hex;
184         co_return true;
185     }
186     else if (generation == Gen::Gen3p5)
187     {
188         uint32_t devID = 0;
189         if (!(co_await getDeviceId(&devID)))
190         {
191             error("program failed at getDeviceId");
192             co_return false;
193         }
194         devID = (devID >> 8) & 0xFF;
195 
196         if (devID >= 0xBA)
197         {
198             *mode = gen3p5;
199         }
200         co_return true;
201     }
202 
203     uint8_t tbuf[defaultBufferSize] = {0};
204     uint8_t rbuf[defaultBufferSize] = {0};
205 
206     tbuf[0] = regHexModeCFG0;
207     tbuf[1] = regHexModeCFG1;
208     if (!(co_await dmaReadWrite(tbuf, rbuf)))
209     {
210         error("getHexMode failed");
211         co_return false;
212     }
213 
214     *mode = (rbuf[0] == 0) ? gen3Legacy : gen3Production;
215 
216     co_return true;
217 }
218 
219 sdbusplus::async::task<bool> ISL69269::getDeviceId(uint32_t* deviceId)
220 {
221     if (deviceId == nullptr)
222     {
223         error("getDeviceId invalid input");
224         co_return false;
225     }
226 
227     uint8_t tbuf[defaultBufferSize] = {0};
228     uint8_t tLen = oneByteLen;
229     uint8_t rbuf[defaultBufferSize] = {0};
230     uint8_t rLen = deviceIdLength + 1;
231 
232     tbuf[0] = pmBusDeviceId;
233 
234     if (!(co_await i2cInterface.sendReceive(tbuf, tLen, rbuf, rLen)))
235     {
236         error("getDeviceId failed");
237         co_return false;
238     }
239 
240     std::memcpy(deviceId, &rbuf[1], deviceIdLength);
241 
242     co_return true;
243 }
244 
245 sdbusplus::async::task<bool> ISL69269::getDeviceRevision(uint32_t* revision)
246 {
247     if (revision == nullptr)
248     {
249         error("getDeviceRevision invalid input");
250         co_return false;
251     }
252 
253     uint8_t tbuf[defaultBufferSize] = {0};
254     uint8_t tlen = oneByteLen;
255     uint8_t rbuf[defaultBufferSize] = {0};
256     uint8_t rlen = deviceRevisionLen + 1;
257 
258     tbuf[0] = pmBusDeviceRev;
259     // NOLINTBEGIN(clang-analyzer-core.uninitialized.Branch)
260     if (!(co_await i2cInterface.sendReceive(tbuf, tlen, rbuf, rlen)))
261     // NOLINTEND(clang-analyzer-core.uninitialized.Branch)
262     {
263         error("getDeviceRevision failed with sendreceive");
264         co_return false;
265     }
266 
267     if (mode == gen3Legacy || mode == gen3p5)
268     {
269         std::memcpy(revision, &rbuf[1], deviceRevisionLen);
270     }
271     else
272     {
273         shiftLeftFromLSB(rbuf + 1, revision);
274     }
275 
276     co_return true;
277 }
278 
279 sdbusplus::async::task<bool> ISL69269::getCRC(uint32_t* sum)
280 {
281     uint8_t tbuf[defaultBufferSize] = {0};
282     uint8_t rbuf[defaultBufferSize] = {0};
283 
284     switch (generation)
285     {
286         case Gen::Gen2:
287             tbuf[0] = gen2RegCRC;
288             break;
289         case Gen::Gen3p5:
290             tbuf[0] = gen3p5RegCRC;
291             break;
292         default:
293             tbuf[0] = regCRC;
294             break;
295     }
296 
297     if (!(co_await dmaReadWrite(tbuf, rbuf)))
298     {
299         error("getCRC failed");
300         co_return false;
301     }
302     std::memcpy(sum, rbuf, sizeof(uint32_t));
303 
304     co_return true;
305 }
306 
307 bool ISL69269::parseImage(const uint8_t* image, size_t imageSize)
308 {
309     size_t nextLineStart = 0;
310     int dcnt = 0;
311 
312     for (size_t i = 0; i < imageSize; i++)
313     {
314         if (image[i] == '\n') // We have a hex file, so we check new line.
315         {
316             char line[40];
317             char xdigit[8] = {0};
318             uint8_t sepLine[32] = {0};
319 
320             size_t lineLen = i - nextLineStart;
321             std::memcpy(line, image + nextLineStart, lineLen);
322             int k = 0;
323             size_t j = 0;
324             for (k = 0, j = 0; j < lineLen; k++, j += 2)
325             {
326                 // Convert two chars into a array of single values
327                 std::memcpy(xdigit, &line[j], 2);
328                 sepLine[k] = (uint8_t)std::strtol(xdigit, NULL, 16);
329             }
330 
331             if (sepLine[0] == recordTypeHeader)
332             {
333                 if (sepLine[3] == pmBusDeviceId)
334                 {
335                     shiftLeftFromMSB(sepLine + 4, &configuration.devIdExp);
336                     debug("device id from configuration: {ID}", "ID", lg2::hex,
337                           configuration.devIdExp);
338                     // GEN3p5 IC_DEVICE_ID Byte ID[1]
339                     if (generation == Gen::Gen3p5 && sepLine[6] >= 0xBA)
340                     {
341                         debug("Gen3p5 hex file format recognized");
342                         configuration.mode = gen3p5;
343                     }
344                 }
345                 else if (sepLine[3] == pmBusDeviceRev)
346                 {
347                     shiftLeftFromMSB(sepLine + 4, &configuration.devRevExp);
348                     debug("device revision from config: {ID}", "ID", lg2::hex,
349                           configuration.devRevExp);
350 
351                     if (generation == Gen::Gen3)
352                     {
353                         // According to programing guide:
354                         // If legacy hex file
355                         // MSB device revision == 0x00 | 0x01
356                         if (configuration.devRevExp < (gen3SWRevMin << 24))
357                         {
358                             debug("Legacy hex file format recognized");
359                             configuration.mode = gen3Legacy;
360                         }
361                         else
362                         {
363                             debug("Production hex file format recognized");
364                             configuration.mode = gen3Production;
365                         }
366                     }
367                 }
368                 else if (sepLine[3] == hexFileRev)
369                 {
370                     debug("Gen2 hex file format recognized");
371                     configuration.mode = gen2Hex;
372                 }
373             }
374             else if (sepLine[0] == recordTypeData)
375             {
376                 if (((sepLine[1] + 2) >= (uint8_t)sizeof(sepLine)))
377                 {
378                     dcnt = 0;
379                     break;
380                 }
381                 // According to documentation:
382                 // 00 05 C2 E7 08 00 F6
383                 //  |  |  |  |  |  |  |
384                 //  |  |  |  |  |  |  - Packet Error Code (CRC8)
385                 //  |  |  |  |  -  - Data
386                 //  |  |  |  - Command Code
387                 //  |  |  - Address
388                 //  |  - Size of data (including Addr, Cmd, CRC8)
389                 //  - Line type (0x00 - Data, 0x49 header information)
390                 configuration.pData[dcnt].len = sepLine[1] - 2;
391                 configuration.pData[dcnt].pec =
392                     sepLine[3 + configuration.pData[dcnt].len];
393                 configuration.pData[dcnt].addr = sepLine[2];
394                 configuration.pData[dcnt].cmd = sepLine[3];
395                 std::memcpy(configuration.pData[dcnt].data, sepLine + 2,
396                             configuration.pData[dcnt].len + 1);
397                 switch (dcnt)
398                 {
399                     case cfgId:
400                         if (configuration.mode != gen3p5)
401                         {
402                             configuration.cfgId = sepLine[4] & 0x0F;
403                             debug("Config ID: {ID}", "ID", lg2::hex,
404                                   configuration.cfgId);
405                         }
406                         break;
407                     case gen3p5cfgId:
408                         if (configuration.mode == gen3p5)
409                         {
410                             configuration.cfgId = sepLine[4];
411                             debug("Config ID: {ID}", "ID", lg2::hex,
412                                   configuration.cfgId);
413                         }
414                         break;
415                     case gen3LegacyCRC:
416                         if (configuration.mode == gen3Legacy)
417                         {
418                             std::memcpy(&configuration.crcExp, &sepLine[4],
419                                         checksumLen);
420                             debug("Config Legacy CRC: {CRC}", "CRC", lg2::hex,
421                                   configuration.crcExp);
422                         }
423                         break;
424                     case gen3ProductionCRC:
425                         if (configuration.mode == gen3Production)
426                         {
427                             std::memcpy(&configuration.crcExp, &sepLine[4],
428                                         checksumLen);
429                             debug("Config Production CRC: {CRC}", "CRC",
430                                   lg2::hex, configuration.crcExp);
431                         }
432                         break;
433                     case gen2CRC:
434                         if (configuration.mode == gen2Hex)
435                         {
436                             std::memcpy(&configuration.crcExp, &sepLine[4],
437                                         checksumLen);
438                             debug("Config Gen2 CRC: {CRC}", "CRC", lg2::hex,
439                                   configuration.crcExp);
440                         }
441                         break;
442                     case gen3p5CRC:
443                         if (configuration.mode == gen3p5)
444                         {
445                             std::memcpy(&configuration.crcExp, &sepLine[4],
446                                         checksumLen);
447                             debug("Config Gen3p5 CRC: {CRC}", "CRC", lg2::hex,
448                                   configuration.crcExp);
449                         }
450                         break;
451                 }
452                 dcnt++;
453             }
454             else
455             {
456                 error("parseImage failed. Unknown recordType");
457                 return false;
458             }
459 
460             nextLineStart = i + 1;
461         }
462     }
463     configuration.wrCnt = dcnt;
464     return true;
465 }
466 
467 bool ISL69269::checkImage()
468 {
469     uint8_t crc8 = 0;
470 
471     for (int i = 0; i < configuration.wrCnt; i++)
472     {
473         crc8 = calcCRC8(configuration.pData[i].data,
474                         configuration.pData[i].len + 1);
475         if (crc8 != configuration.pData[i].pec)
476         {
477             debug(
478                 "Config line: {LINE}, failed to calculate CRC. Have {HAVE}, Want: {WANT}",
479                 "LINE", i, "HAVE", lg2::hex, crc8, "WANT", lg2::hex,
480                 configuration.pData[i].pec);
481             return false;
482         }
483     }
484 
485     return true;
486 }
487 
488 sdbusplus::async::task<bool> ISL69269::program()
489 {
490     uint8_t tbuf[programBufferSize] = {0};
491     uint8_t rbuf[programBufferSize] = {0};
492     uint8_t rlen = zeroByteLen;
493 
494     for (int i = 0; i < configuration.wrCnt; i++)
495     {
496         std::memcpy(tbuf, configuration.pData[i].data + 1,
497                     configuration.pData[i].len);
498 
499         if (!(co_await i2cInterface.sendReceive(
500                 tbuf, configuration.pData[i].len, rbuf, rlen)))
501         {
502             error("program failed at writing data to voltage regulator");
503         }
504     }
505 
506     if (!(co_await getProgStatus()))
507     {
508         error("program failed at getProgStatus");
509         co_return false;
510     }
511 
512     co_return true;
513 }
514 
515 sdbusplus::async::task<bool> ISL69269::getProgStatus()
516 {
517     uint8_t tbuf[programBufferSize] = {0};
518     uint8_t rbuf[programBufferSize] = {0};
519     int retry = 3;
520 
521     if (generation == Gen::Gen2)
522     {
523         tbuf[0] = gen2RegProgStatus;
524         tbuf[1] = gen2RegProgStatus;
525     }
526     else if (generation == Gen::Gen3p5)
527     {
528         tbuf[0] = regGen3p5ProgStatus;
529         tbuf[1] = 0x00;
530     }
531     else
532     {
533         tbuf[0] = regProgStatus;
534         tbuf[1] = 0x00;
535     }
536 
537     do
538     {
539         // NOLINTBEGIN(clang-analyzer-core.uninitialized.Branch)
540         if (!(co_await dmaReadWrite(tbuf, rbuf)))
541         // NOLINTEND(clang-analyzer-core.uninitialized.Branch)
542         {
543             error("getProgStatus failed on dmaReadWrite");
544             co_return false;
545         }
546 
547         if (rbuf[0] & 0x01)
548         {
549             debug("Programming successful");
550             break;
551         }
552         if (--retry == 0)
553         {
554             if ((!(rbuf[1] & 0x1)) || (rbuf[1] & 0x2))
555             {
556                 error("programming the device failed");
557             }
558             if (!(rbuf[1] & 0x4))
559             {
560                 error(
561                     "HEX file contains more configurations than are available");
562             }
563             if (!(rbuf[1] & 0x8))
564             {
565                 error(
566                     "A CRC mismatch exists within the configuration data. Programming failed before  TP banks are consumed");
567             }
568             if (!(rbuf[1] & 0x10))
569             {
570                 error(
571                     "CRC check fails on the OTP memory. Programming fails after OTP banks are consumed");
572             }
573             if (!(rbuf[1] & 0x20))
574             {
575                 error("Programming fails after OTP banks are consumed.");
576             }
577 
578             error("failed to program the device after exceeding retries");
579             co_return false;
580         }
581         co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(1));
582     } while (retry > 0);
583 
584     co_return true;
585 }
586 
587 sdbusplus::async::task<bool> ISL69269::restoreCfg()
588 {
589     uint8_t tbuf[defaultBufferSize] = {0};
590     uint8_t rbuf[defaultBufferSize] = {0};
591 
592     tbuf[0] = regRestoreCfg;
593     tbuf[1] = configuration.cfgId;
594 
595     debug("Restore configuration ID: {ID}", "ID", lg2::hex,
596           configuration.cfgId);
597 
598     if (!(co_await dmaReadWrite(tbuf, rbuf)))
599     {
600         error("restoreCfg failed at dmaReadWrite");
601         co_return false;
602     }
603 
604     co_return true;
605 }
606 
607 sdbusplus::async::task<bool> ISL69269::verifyImage(const uint8_t* image,
608                                                    size_t imageSize)
609 {
610     uint8_t mode = 0xFF;
611     uint8_t remain = 0;
612     uint32_t devID = 0;
613     uint32_t devRev = 0;
614     uint32_t crc = 0;
615 
616     if (!(co_await getHexMode(&mode)))
617     {
618         error("program failed at getHexMode");
619         co_return false;
620     }
621 
622     if (!parseImage(image, imageSize))
623     {
624         error("verifyImage failed at parseImage");
625         co_return false;
626     }
627 
628     if (!checkImage())
629     {
630         error("verifyImage failed at checkImage");
631         co_return false;
632     }
633 
634     if (mode != configuration.mode)
635     {
636         error(
637             "program failed with mode of device and configuration are not equal");
638         co_return false;
639     }
640 
641     if (!(co_await getRemainingWrites(&remain)))
642     {
643         error("program failed at getRemainingWrites");
644         co_return false;
645     }
646 
647     if (!remain)
648     {
649         error("program failed with no remaining writes left on device");
650         co_return false;
651     }
652 
653     if (!(co_await getDeviceId(&devID)))
654     {
655         error("program failed at getDeviceId");
656         co_return false;
657     }
658 
659     if (devID != configuration.devIdExp)
660     {
661         error(
662             "program failed with not matching device id of device and config");
663         co_return false;
664     }
665 
666     if (!(co_await getDeviceRevision(&devRev)))
667     {
668         error("program failed at getDeviceRevision");
669         co_return false;
670     }
671     debug("Device revision read from device: {REV}", "REV", lg2::hex, devRev);
672 
673     switch (mode)
674     {
675         case gen3Legacy:
676             if (((devRev >> 24) >= gen3SWRevMin) &&
677                 (configuration.devRevExp <= 0x1))
678             {
679                 debug("Legacy mode revision checks out");
680             }
681             else
682             {
683                 error(
684                     "revision requirements for legacy mode device not fulfilled");
685             }
686             break;
687         case gen3Production:
688             if (((devRev >> 24) >= gen3SWRevMin) &&
689                 (configuration.devRevExp >= gen3SWRevMin))
690             {
691                 debug("Production mode revision checks out");
692             }
693             else
694             {
695                 error(
696                     "revision requirements for production mode device not fulfilled");
697             }
698             break;
699         case gen2Hex:
700             if (devRev >= gen2RevMin)
701             {
702                 debug("Gen2 mode revision checks out");
703             }
704             else
705             {
706                 error("revision requirements for Gen2 device not fulfilled");
707             }
708             break;
709         case gen3p5:
710             if (((devRev >> 24) >= gen3p5HWRevMin) &&
711                 ((devRev >> 24) <= gen3p5HWRevMax))
712             {
713                 debug("Gen3p5 revision checks out");
714             }
715             else
716             {
717                 error("revision requirements for Gen3p5 device not fulfilled");
718             }
719             break;
720     }
721 
722     if (!(co_await getCRC(&crc)))
723     {
724         error("program failed at getCRC");
725         co_return false;
726     }
727 
728     debug("CRC from device: {CRC}", "CRC", lg2::hex, crc);
729     debug("CRC from config: {CRC}", "CRC", lg2::hex, configuration.crcExp);
730 
731     if (crc == configuration.crcExp)
732     {
733         error("program failed with same CRC value at device and configuration");
734         co_return false;
735     }
736 
737     co_return true;
738 }
739 
740 bool ISL69269::forcedUpdateAllowed()
741 {
742     return true;
743 }
744 
745 sdbusplus::async::task<bool> ISL69269::updateFirmware(bool force)
746 {
747     (void)force;
748     // NOLINTBEGIN(clang-analyzer-core.uninitialized.Branch)
749     if (!(co_await program()))
750     // NOLINTEND(clang-analyzer-core.uninitialized.Branch)
751     {
752         error("programing ISL69269 failed");
753         co_return false;
754     }
755 
756     co_return true;
757 }
758 
759 } // namespace phosphor::software::VR
760