1.. _seed-0117: 2 3============= 40117: I3C 5============= 6.. seed:: 7 :number: 117 8 :name: I3C 9 :status: Accepted 10 :proposal_date: 2023-10-30 11 :authors: Jack Chen 12 :cl: 178350 13 :facilitator: Alexei Frolov 14 15------- 16Summary 17------- 18A new peripheral protocol, I3C (pronounced eye-three-see) was introduced to 19electronic world and it has been widely accepted by SoC and sensor 20manufacturers. This seed is to propose a new front-end library pw_i3c, to help 21communicate with devices on I3C bus. 22 23---------- 24Motivation 25---------- 26Though widely used, I²C peripheral bus has several significant shortages, for 27example the low bus speed, extra physical line to carry interrupt from each 28device on the I²C bus, etc. To cope with higher requirements and to address 29shortages of I²C, MIPI proposed a new fast, low-power and two-wire peripheral 30protocol, I3C. 31 32I3C could be regarded as improved I²C. But in the meantime, it is a new protocol 33which is different to I²C in both hardware and software. Some important 34differences include: 35 361. I3C SDA uses open-drain mode when necessary for legacy I²C compatibility, 37 but switches to push-pull outputs whenever possible. 382. I3C SCL runs in only in pull-pull mode and can only be driven by I3C 39 initiator. 403. I3C devices supports dynamic address assignment (DAA) and has higher address 41 requirements. 424. I3C needs bus initialization and DAA before it is ready to use. 435. I3C has a standardized command set, called common command codes (CCC). 446. I3C supports In-Band interrupt and hot-join. 457. I3C device is identified by a 48-bit Provisioned ID (Manufacturer ID + Part 46 ID + Instance ID). 47 48In conclusion, it is worth providing an independent library pw_i3c to help 49initialize I3C initiator and communicate with I3C devices on the bus. 50 51-------------------------- 52Proposal (Detailed design) 53-------------------------- 54Since I3C is very similar to I²C, following proposal is to create a standalone 55library pw_i3c, which shares a similar structure as pw_i2c. 56 57device type 58----------- 59 60The ``DeviceType`` is used to help differentiate legacy I²C devices and I3C 61devices on one I3C bus. 62 63.. code-block:: c++ 64 65 enum class DeviceType : bool { 66 kI2c, 67 kI3c, 68 }; 69 70Address 71------- 72 73The ``Address`` is a helper class to represent addresses which are used by 74``pw_i3c`` APIs. In I3C protocol, addresses are differentiated by I²C (static) 75address and I3C (active or dynamic) address. The reason why device type is 76embedded into address is that transactions for I²C and I3C devices are 77different. So ``Initiator`` could tell the device type just by the provided 78``Address`` object. 79 80Apart from creating and checking ``Address`` at compile time, a helper 81constructor to create and check ``Address`` at runtime is created, with 82following reasons: 83 841. A new active/dynamic address could be assigned to I3C devices at run time. 852. New devices could hot-join the bus at run time. 86 87.. code-block:: c++ 88 89 class Address { 90 public: 91 static constexpr uint8_t kHotJoinAddress = 0x02; 92 static constexpr uint8_t kBroadcastAddress = 0x7E; 93 static constexpr uint8_t kStaticMaxAddress = 0x7F; 94 // For I3C, the active (dynamic) address restriction is dynamic (depending 95 // on devices on the bus, details can be found in "MIPI I3C Basic 96 // Specification Version 1.1.1" chapter 5.1.2.2.5). But to simplify the 97 // design, the strictest rule is applied. And there will be 108 addresses 98 // free for dynamic address chosen. 99 static constexpr uint8_t kDynamicMinAddress = 0x08; 100 static constexpr uint8_t kDynamicMaxAddress = 0x7D; 101 102 // Helper constructor to ensure the address fits in the address space at 103 // compile time, skipping the construction time assert. 104 template <DeviceType kDeviceType, uint8_t kAddress> 105 static constexpr Address Create() { 106 static_assert(!IsOutOfRange(kDeviceType, kAddress)); 107 static_assert((DeviceType::kI2c == kDeviceType) || 108 !SingleBitErrorDetection(kAddress)); 109 return Address{kDeviceType, kAddress}; 110 } 111 112 // Helper constructor to create the address at run time. 113 // Returns std::nullopt if provided type and address does not fit address 114 // rules. 115 static constexpr std::optional<Address> Create( 116 DeviceType device_type, uint8_t address) { 117 if (IsOutOfRange(device_type, address)) { 118 return std::nullopt; 119 } 120 if (DeviceType::kI3c == device_type && SingleBitErrorDetection(address)) { 121 return std::nullopt; 122 } 123 return Address{type, address}; 124 } 125 126 // Return the type of address. 127 constexpr DeviceType GetType() const; 128 129 // Return the address. 130 constexpr uint8_t GetAddress() const; 131 132 private: 133 static constexpr uint8_t kAddressMask = 0x7F; 134 static constexpr int kTypeShift = 7; 135 static constexpr uint8_t kTypeMask = 0x01; 136 137 constexpr Address(DeviceType type, uint8_t address) 138 : packed_address_{Pack(type, address)} {} 139 140 uint8_t packed_address_; 141 }; 142 143Ccc 144--- 145 146Common Command Codes are categorized into broadcast(Command Codes from 0x00 to 1470x7F) and direct(Command Codes from 0x80 to 0xFE). The rational behind it is 148broadcast CCC can only be write and is executed in one transaction, but direct 149CCC can be both write and read, and is executed in two transactions. We can 150eliminate extra CCC type check in initiator CCC API. 151 152.. code-block:: c++ 153 154 enum class CccBroadcast : uint8_t { 155 kEnc = 0x00, 156 kDisec = 0x01, 157 kEntdas0 = 0x02, 158 ... 159 kSetxtime = 0x28, 160 kSetaasa = 0x29, 161 } 162 163 enum class CccDirect : uint8_t { 164 kEnc = 0x80, 165 kDisec = 0x81, 166 kEntas0 = 0x82, 167 ... 168 kSetxtime = 0x98, 169 kGetxtime = 0x99, 170 }; 171 172 inline constexpr uint8_t kCccDirectBit = 7; 173 174 enum class CccAction : bool { 175 kWrite, 176 kRead, 177 }; 178 179Initiator 180--------- 181 182.. inclusive-language: disable 183 184Similar as ``pw::i2c``, ``Initiator`` is the common, base driver interface for 185initiating thread-safe transactions with devices on an I3C bus. Other 186documentation may call this style of interface an “master”, “central”, or 187“controller”. 188 189.. inclusive-language: enable 190 191The main difference by comparison with I²C, is I3C initiator needs a bus 192initialization and dynamic address assignment (DAA) step, before it is fully 193functional. And after first bus initialization, I3C initiator should be able to 194do bus re-initialization anytime when the bus is free. However, different 195backend implementations may deal with this part differently. For example, Linux 196does not expose I3C bus to userspace, which means users cannot control bus 197initialization. NXP Mcuxpresso SDK exposes I3C as a pure library to users, so it 198is users' responsibility to initialize the bus and perform DAA to get a usable 199initiator. Zephyr provides more functions than Linux regarding I3C, which 200makes I3C usage in Zephyr looks more likely to NXP Mcuxpresso SDK. 201 202Considering the complexity of different backend implementations of I3C bus, it 203is better to have an "Initiator Maker" to take care of making an I3C 204``Initiator``. And this is not considered in the first version of ``pw_i3c`` 205design. 206 207.. code-block:: c++ 208 209 // PID is the unique identifier to an I3C device. 210 struct Pid { 211 uint16_t manuf_id = 0; 212 uint16_t part_id = 0; 213 uint16_t instance_id = 0; 214 215 friend constexpr bool operator==(Pid const& lhs, Pid const& rhs) { 216 return (lhs.manuf_id == rhs.manuf_id) && (lhs.part_id == rhs.part_id) && 217 (lhs.instance_id == rhs.instance_id); 218 } 219 friend constexpr bool operator!=(Pid const& lhs, Pid const& rhs) { 220 return (lhs.manuf_id != rhs.manuf_id) || (lhs.part_id != rhs.part_id) || 221 (lhs.instance_id != rhs.instance_id); 222 } 223 224 // Concat manuf_id, part_id and instance_id to a pid in 64-bit. 225 constexpr uint64_t AsUint64(Pid const pid) { 226 return (static_cast<uint64_t>(pid.manuf_id) << 33) | 227 (static_cast<uint64_t>(pid.part_id) << 16) | 228 (static_cast<uint64_t>(pid.instance_id) << 12); 229 } 230 231 // Split a 64-bit pid into manuf_id, part_id and instance_id (struct Pid). 232 static constexpr Pid FromUint64(const uint64_t pid) { 233 return Pid{ 234 .manuf_id = ExtractBits<uint16_t, 47, 33>(pid), 235 .part_id = ExtractBits<uint16_t, 31, 16>(pid), 236 .instance_id = ExtractBits<uint16_t, 15, 12>(pid)}; 237 } 238 239 }; 240 241 // I3C supports in-band interrupt (IBI), but generalize the name to cover 242 // traditional interrupts. 243 // For IBI, the argument can be used to store data transferred. 244 // If the handler is queued in a workqueue, the data could be of any length. 245 // If the handler is executed inside CPU ISR directly, the data should only 246 // be mandatory data byte. 247 using InterruptHandler = ::pw::Function<void(ByteSpan)>; 248 249 class Initiator { 250 public: 251 virtual ~Initiator() = default; 252 253 // Pid is the unique identifier to an I3C device and it should be known to 254 // users through datasheets, like static addresses to I²C or I3C devices. 255 // But active (dynamic) address to an I3C device is changeable during 256 // run-time. Users could use this API to retrieve active address through 257 // PID. 258 // 259 // There are other information which users may be interested to know about 260 // an I3C device, like Bus Characteristics Register (BCR) and Device 261 // Characteristic Register(s) (DCR). But they can be read through direct 262 // read CCC API (ReadDirectCcc). 263 // 264 // Returns: 265 // Dynamic Address - Success. 266 // NOT_FOUND - Provided pid does not match with any active i3c device. 267 Result<Address> RetrieveDynamicAddressByPid(Pid pid); 268 269 // Perform a broadcast CCC transaction. 270 // ccc_id: the broadcast CCC ID. 271 // buffer: payload to broadcast. 272 // 273 // Returns: 274 // OK - Success. 275 // INVALID_ARGUMENT - provided ccc_id is not supported. 276 // UNAVAILABLE - NACK condition occurred, meaning there are no active I3C 277 // devices on the bus. 278 // Other status codes as defined by backend. 279 Status WriteBroadcastCcc(CccBroadcast ccc_id, ConstByteSpan buffer); 280 Status WriteBroadcastCcc(CccBroadcast ccc_id, 281 const void* buffer, 282 size_t size_bytes); 283 284 // Perform a direct write CCC transaction. 285 // ccc_id: the direct CCC ID. 286 // device_address: the address which the CCC targets for. 287 // buffer: payload to write. 288 // 289 // Returns: 290 // OK - Success. 291 // INVALID_ARGUMENT - provided ccc_id is not supported, or device_address is 292 // for I3C devices. 293 // UNAVAILABLE - NACK condition occurred, meaning there is no active I3C 294 // device with the provided device_address or it is busy now. 295 // Other status codes as defined by backend. 296 Status WriteDirectCcc(CccDirect ccc_id, 297 Address device_address, 298 ConstByteSpan buffer); 299 Status WriteDirectCcc(CccDirect ccc_id, 300 Address device_address, 301 const void* buffer, 302 size_t size_bytes); 303 304 // Perform a direct read CCC transaction. 305 // ccc_id: the direct CCC ID. 306 // device_address: the address which the CCC targets for. 307 // buffer: payload to read. 308 // 309 // Returns: 310 // OK - Success. 311 // INVALID_ARGUMENT - provided ccc_id is not supported, or device_address is 312 // for I3C devices. 313 // UNAVAILABLE - NACK condition occurred, meaning there is no active I3C 314 // device with the provided device_address or it is busy now. 315 // Other status codes as defined by backend. 316 Status ReadDirectCcc(CccDirect ccc_id, 317 Address device_address, 318 ByteSpan buffer); 319 Status ReadDirectCcc(CccDirect ccc_id, 320 Address device_address, 321 void* buffer, 322 size_t size_bytes); 323 324 // Write bytes and read bytes (this is normally executed in two independent 325 // transactions). 326 // 327 // Timeout is no longer needed in I3C transactions because only I3C 328 // initiator drives the clock in push-pull mode, and devices on the bus 329 // cannot stretch the clock. 330 // 331 // Returns: 332 // Ok - Success. 333 // UNAVAILABLE - NACK condition occurred, meaning the addressed device did 334 // not respond or was unable to process the request. 335 // Other status codes as defined by backend. 336 Status WriteReadFor(Address device_address, 337 ConstByteSpan tx_buffer, 338 ByteSpan rx_buffer); 339 Status WriteReadFor(I3cResponder device, 340 const void* tx_buffer, 341 size_t tx_size_bytes, 342 void* rx_buffer, 343 size_t rx_size_bytes); 344 345 // Write bytes. 346 // 347 // Returns: 348 // OK - Success. 349 // UNAVAILABLE - NACK condition occurred, meaning the addressed device did 350 // not respond or was unable to process the request. 351 // Other status codes as defined by backend. 352 Status WriteFor(Address device_address, ConstByteSpan tx_buffer); 353 Status WriteFor(Address device_address, 354 const void* tx_buffer, 355 size_t tx_size_bytes); 356 357 // Read bytes. 358 // 359 // Returns: 360 // OK - Success. 361 // UNAVAILABLE - NACK condition occurred, meaning the addressed device did 362 // not respond or was unable to process the request. 363 // Other status codes as defined by backend. 364 Status ReadFor(Address device_address, ByteSpan rx_buffer); 365 Status ReadFor(Address device_address, 366 void* rx_buffer, 367 size_t rx_size_bytes); 368 369 // Probes the device for an ACK after only writing the address. 370 // This is done by attempting to read a single byte from the specified 371 // device. 372 // 373 // Returns: 374 // OK - Success. 375 // UNAVAILABLE - NACK condition occurred, meaning the addressed device did 376 // not respond or was unable to process the request. 377 Status ProbeDeviceFor(Address device_address); 378 379 // Sets a (IBI) handler to execute when an interrupt is triggered from a 380 // device with the provided address. Handler for one address should be 381 // registered only once, unless it is cleared. Registration twice with 382 // same address should fail. 383 // 384 // Note that hot-join handler could be registered with this function since 385 // hot-join is sent through IBI. 386 // 387 // This handler is finally executed by I3C initiator, which means it may 388 // include any valid I3C actions (write and read). When I3C write/read 389 // happens, the interrupt handler is more like an I3C transaction than a 390 // traditional interrupt. Different I3C initiators may execute the handler 391 // in different ways. Some may queue the work on a workqueue and some may 392 // execute the handler directly inside IBI IRQ. Users should be aware of the 393 // backend algorithm and when execution happens in IBI IRQ, they should just 394 // read the mandatory data byte out through the handler and perform other 395 // actions in a different thread. 396 // 397 // Warning: 398 // This method is not thread-safe and cannot be used in interrupt handlers. 399 // 400 // Returns: 401 // OK - The interrupt handler was configured. 402 // INVALID_ARGUMENT - The handler is empty, or the handler is for IBI but 403 // the address is not for an I3C device. 404 // Other status codes as defined by the backend. 405 Status SetInterruptHandler(Address device_address, 406 InterruptHandler&& handler); 407 408 // Clears the interrupt handler. 409 // 410 // Warning: 411 // This method is not thread-safe and cannot be used in interrupt handlers. 412 // 413 // Returns: 414 // OK - The interrupt handler was cleared 415 // INVALID_ARGUMENT - the handler is for IBI but the address is not for an 416 // I3C device. 417 // Other status codes as defined by the backend. 418 Status ClearInterruptHandler(Address device_address); 419 420 // Enables interrupts which will trigger the interrupt handler. 421 // 422 // Warning: 423 // This method is not thread-safe and cannot be used in interrupt handlers. 424 // 425 // Preconditions: 426 // A handler has been set using `SetInterruptHandler()`. 427 // 428 // Returns: 429 // OK - The interrupt handler was enabled. 430 // INVALID_ARGUMENT - the handler is for IBI but the address is not for an 431 // I3C device. 432 // Other status codes as defined by the backend. 433 Status EnableInterruptHandler(Address device_address); 434 435 // Disables interrupts which will trigger the interrupt handler. 436 // 437 // Warning: 438 // This method is not thread-safe and cannot be used in interrupt handlers. 439 // 440 // Preconditions: 441 // A handler has been set using `SetInterruptHandler()`. 442 // 443 // Returns: 444 // OK - The interrupt handler was disabled. 445 // INVALID_ARGUMENT - the handler is for IBI but the address is not for an 446 // I3C device. 447 // Other status codes as defined by the backend. 448 Status DisableInterruptHandler(Address device_address); 449 450 private: 451 virtual Result<Address> DoRetrieveDynamicAddressByPid(Pid pid) = 0; 452 virtual Status DoTransferCcc(CccAction read_or_write, 453 uint8_t ccc_id, 454 std::optional<Address> device_address, 455 ByteSpan buffer) = 0; 456 virtual Status DoWriteReadFor(Address device_address, 457 ConstByteSpan tx_buffer, 458 ByteSpan rx_buffer) = 0; 459 virtual Status DoSetInterruptHandler(Address address, 460 InterruptHandler&& handler) = 0; 461 virtual Status DoEnableInterruptHandler(Address address, bool enable) = 0; 462 }; 463 464Device 465------ 466 467Same as ``pw::i2c::Device``, a ``Device`` class is used in ``pw::i3c`` to 468write/read arbitrary chunks of data over a bus to a specific device, or perform 469other I3C operations, e.g. direct CCC. 470Though PID is the unique identifier for I3C devices, considering backward 471compatibility with I²C devices, this object also wraps the Initiator API with 472an active ``Address``. Application should initiate or be notified the 473``Address`` change and update the ``Address`` in ``Device`` object. 474 475.. code-block:: c++ 476 477 class Device { 478 public: 479 // It is users' responsibility to get and pass the active (dynamic) address 480 // when creating a ``Device``. If the dynamic address of an I3C device is 481 // unknown, users could get it through initiator: 482 // pw::i3c::Initiator::RetrieveDynamicAddressByPid(); 483 constexpr Device(Initiator& initiator, Address device_address, Pid pid) 484 : initiator_(initiator), 485 device_address_(device_address), 486 pid_(pid) {} 487 488 // For I2C devices connected to I3C bus, pid_ is default-initialized to be 489 // std::nullopt. 490 constexpr Device(Initiator& initiator, Address device_address) 491 : initiator_(initiator), 492 device_address_(device_address) {} 493 494 Device(const Device&) = delete; 495 Device(Device&&) = default; 496 ~Device() = default; 497 498 // Perform a direct write CCC transaction. 499 // ccc_id: the direct CCC ID. 500 // buffer: payload to write. 501 // 502 // Returns: 503 // OK - Success. 504 // INVALID_ARGUMENT - provided ccc_id is not supported, or device_address_ 505 // is not for I3C devices. 506 // UNAVAILABLE - NACK condition occurred, meaning there is no active I3C 507 // device with the provided device_address or it is busy now. 508 // Other status codes as defined by backend. 509 Status WriteDirectCcc(CccDirect ccc_id, ConstByteSpan buffer); 510 Status WriteDirectCcc(CccDirect ccc_id, 511 const void* buffer, 512 size_t size_bytes); 513 514 // Perform a direct read CCC transaction. 515 // ccc_id: the direct CCC ID. 516 // buffer: payload to read. 517 // 518 // Returns: 519 // OK - Success. 520 // INVALID_ARGUMENT - provided ccc_id is not supported, or device_address_ 521 // is not for I3C devices. 522 // UNAVAILABLE - NACK condition occurred, meaning there is no active I3C 523 // device with the provided device_address or it is busy now. 524 // Other status codes as defined by backend. 525 Status ReadDirectCcc(CccDirect ccc_id, ByteSpan buffer); 526 Status ReadDirectCcc(CccDirect ccc_id, 527 const void* buffer, 528 size_t size_bytes); 529 530 // Write bytes and read bytes (this is normally executed in two independent 531 // transactions). 532 // 533 // Timeout is no longer needed in I3C transactions because only I3C 534 // initiator drives the clock in push-pull mode, and devices on the bus 535 // cannot stretch the clock. 536 // 537 // Returns: 538 // OK - Success. 539 // UNAVAILABLE - NACK condition occurred, meaning the addressed device did 540 // not respond or was unable to process the request. 541 // Other status codes as defined by backend. 542 Status WriteReadFor(ConstByteSpan tx_buffer, ByteSpan rx_buffer); 543 Status WriteReadFor(const void* tx_buffer, 544 size_t tx_size_bytes, 545 void* rx_buffer, 546 size_t rx_size_bytes); 547 548 // Write bytes. 549 // 550 // Returns: 551 // OK - Success. 552 // UNAVAILABLE - NACK condition occurred, meaning the addressed device did 553 // not respond or was unable to process the request. 554 // Other status codes as defined by backend. 555 Status WriteFor(ConstByteSpan tx_buffer); 556 Status WriteFor(const void* tx_buffer, size_t tx_size_bytes); 557 558 // Read bytes. 559 // 560 // Returns: 561 // Ok - Success. 562 // UNAVAILABLE - NACK condition occurred, meaning the addressed device did 563 // not respond or was unable to process the request. 564 // Other status codes as defined by backend. 565 Status ReadFor(ByteSpan rx_buffer); 566 Status ReadFor(void* rx_buffer, size_t rx_size_bytes); 567 568 // Probes the device for an ACK after only writing the address. 569 // This is done by attempting to read a single byte from this device. 570 // 571 // Returns: 572 // Ok - Success. 573 // UNAVAILABLE - NACK condition occurred, meaning the addressed device did 574 // not respond or was unable to process the request. 575 Status ProbeFor(); 576 577 // Sets a (IBI) handler to execute when an interrupt is triggered from a 578 // device with the provided address. Handler for one address should be 579 // registered only once, unless it is cleared. Registration twice with 580 // same address should fail. 581 // 582 // This handler is finally executed by I3C initiator, which means it may 583 // include any valid I3C actions (write and read). When I3C write/read 584 // happens, the interrupt handler is more like an I3C transaction than a 585 // traditional interrupt. Different I3C initiators may execute the handler 586 // in different ways. Some may queue the work on a workqueue and some may 587 // execute the handler directly inside IBI IRQ. Users should be aware of the 588 // backend algorithm and when execution happens in IBI IRQ, they should just 589 // read the mandatory data byte out through the handler and perform other 590 // actions in a different thread. 591 // 592 // Warning: 593 // This method is not thread-safe and cannot be used in interrupt handlers. 594 // Do not register hot-join handler for I3C devices as hot-join handler is 595 // initiator specific, not for a single device. 596 // 597 // Returns: 598 // OK - The interrupt handler was configured. 599 // INVALID_ARGUMENT - The handler is empty, or the handler is for IBI but 600 // the device is an I3C device. 601 // Other status codes as defined by the backend. 602 Status SetInterruptHandler(InterruptHandler&& handler); 603 604 // Clears the interrupt handler. 605 // 606 // Warning: 607 // This method is not thread-safe and cannot be used in interrupt handlers. 608 // 609 // Returns: 610 // OK - The interrupt handler was cleared 611 // INVALID_ARGUMENT - the handler is for IBI but the device is an I3C device. 612 // Other status codes as defined by the backend. 613 Status ClearInterruptHandler(); 614 615 // Enables interrupts which will trigger the interrupt handler. 616 // 617 // Warning: 618 // This method is not thread-safe and cannot be used in interrupt handlers. 619 // 620 // Preconditions: 621 // A handler has been set using `SetInterruptHandler()`. 622 // 623 // Returns: 624 // OK - The interrupt handler was enabled. 625 // INVALID_ARGUMENT - the handler is for IBI but the device is an I3C device. 626 // Other status codes as defined by the backend. 627 Status EnableInterruptHandler(); 628 629 // Disables interrupts which will trigger the interrupt handler. 630 // 631 // Warning: 632 // This method is not thread-safe and cannot be used in interrupt handlers. 633 // 634 // Preconditions: 635 // A handler has been set using `SetInterruptHandler()`. 636 // 637 // Returns: 638 // OK - The interrupt handler was disabled. 639 // INVALID_ARGUMENT - the handler is for IBI but the device is an I3C device. 640 // Other status codes as defined by the backend. 641 Status DisableInterruptHandler(); 642 643 // Update device address during run-time actively. 644 // 645 // Warning: 646 // This function is dedicatedly to I3C devices. 647 void UpdateAddressActively(Address new_address); 648 649 // Update device address during run-time Passively. 650 // initiator_ will be responsible for retrieving the dynamic address and 651 // substitute the device_address_. 652 // 653 // Warning: 654 // This function is dedicatedly to I3C devices. 655 // 656 // Returns: 657 // OK - Success. 658 // UNIMPLEMENTED - pid is empty (I²C device). 659 // NOT_FOUND - initiator_ fails to retrieve dynamic address based on pid_. 660 Status UpdateAddressPassively(); 661 662 private: 663 Initiator& initiator_; 664 Address device_address_; 665 std::optional<Pid> pid_; 666 }; 667 668------------ 669Alternatives 670------------ 671Since I3C is similar to I²C and pw_i3c is similar to pw_i2c, instead of creating 672a standalone library, an alternative solution is to combine pw_i3c and pw_i2c, 673and providing a single library pw_ixc, or other suitable names. And in this 674comprehensive library, I3C-related features could be designed to be optional. 675 676On one hand, this solution could reuse a lot of code and simplify some work in 677user level, if users want to abstract usage of I²C and I3C in application. 678On the other hand, it also brings churn to existing projects using pw_i2c, and 679ambiguity and confusion in the long run (I²C is mature, but I3C is new and 680actively improving). 681 682-------------- 683Open Questions 684-------------- 6851. As mentioned in the description of I3C ``Initiator`` class, the creation of a 686 fully functional ``Initiator`` would be handled by a different class. 687 6882. Because there are two types of ``Address`` in I3C, the static and dynamic, 689 the ``pw::i3c::Address`` is not compatible with ``pw::i2c::Address``. So when 690 users try to create an ``Address`` for an I²C device, they need to carefully 691 choose the correct class depending on which bus the device is connected to. 692 This class may cause bigger concern if ``Address`` is needed to be shared 693 through interface in application code. 694 But the problem is resolvable by templating ``Address`` in caller code. 695 Also, we can have a helper function in ``pw::i3c::Address``, which consumes a 696 ``pw::i2c::Addres``s and create a ``pw::i3c::Address``. This helper will be 697 added and discussed further in a following patch. 698 6993. ``DeviceType`` is embedded into ``Address``. So ``pw::i3c::Initiator`` could 700 tell the device type (I²C or I3C) based on provided ``Address``. But this is 701 not necessary because Initiator has performed DAA during initialization so it 702 should know which addresses have been assigned to I3C devices. 703 In this case, the only advantage of this design is to help applying address 704 restriction during creating ``Address`` object. Should address restriction be 705 taken care of by HAL? Though fewer, I²C has reserved addresses, too, but they 706 are not checked in ``pw::i2c::Address``. 707 7084. ``RegisterDevice`` for I3C is the same as I²C, in protocol. To read/write 709 from a register, the ``Initiator`` sends an active address on the bus. Once 710 acknowledged, it will send the register address followed by data. 711 So the ideal design is to abstract ``RegisterDevice`` across I²C and I3C, or 712 maybe even other peripheral buses (e.g. SPI and DSI). However, the underlying 713 register operation functions are different. It is better to be handled in a 714 separate SEED. 715