 Welcome everyone, and thanks for attending this session about the I3C subsystem. So, I'm Michel. I work at Bootlean as an Embedded Linux Engineer. I'm involved in the community, primarily as a nondementainer, because I also sent a few drivers, and more recently I contributed to the I3C subsystem. So, the I3C bus is yet another serial bus. And to answer that question, I would like to discuss this small situation. So, you are a hardware engineer. You want to wear a simple device on a custom board. So, what bus will you use? There are many of them available out there. Of course, you'll have to look at what your targeted device supports. You'll have to think if you have power consumption targets, throughputs, wiring constraints, and maybe as well the number of devices you want to wire. Among all the possible serial buses that could do that, certain are too simple, maybe too slow, too old, or not suitable for embedded systems. Generally speaking, I3C and SPY, they work most of the time for this kind of device. But, you think further, and you want maybe to wear several devices, and you have bandwidth constraints, maybe the devices you want to plug in are a bit complex. Will you still use buses such as SPY or I3C? Because I3C is pretty slow. It runs at 400 kHz, while SPY is fast. It can run at up to 100 MHz. The problem with slow exchanges is that they will, of course, last longer. On one side, it increases the power consumption per bit transmitted. And also it means that you'll have extra delays when accessing other devices. So SPY could work, but SPY needs one physical chip select per device, which is a major issue if you have a lot of devices, because wiring constraints are very important when thinking about hardware design. I2C on its side surface from address collision issues, because when you buy a device, most of the time it only supports a single or a couple of addresses if you have the chance of having an extra pin that can be tied up or low, but no more. If you need asynchronous signaling, you'll need an additional wire as well. So these buses don't have any type of low power modes, nor any device class that could help managing several devices the same way. So here comes I3C. I3C stands for Improved Interintegrated Security. So Improved I2C, basically. It's an EPI Alliance specification targeting automotive, consumer electronics under the IoT market. It's a two-wire bus with much more features than just I2C. It has a reasonably high throughput, a lot of internal commands for bus management, the possibility to provide devices dynamically to all the devices. It has a self-description feature, inbound signaling, hot join, control handover, low power modes, and so on and so forth. And as a major feature, it is backward compatible with I2C. So why would it be backward compatible? Because this brings a lot of constraints. Well, first, it's in order to ease axiom tones of that bus and, of course, improve the reusability of the existing devices. There are plenty of I2C devices out there, and it would be a good thing to have them working natively on the I3C bus because at the beginning you could at least change the controller from an I2C controller to a 3C controller. You would use I2C devices on it, that's not a problem. And as I3C devices appear, you could replace them one after the other until you end up with a pure I3C bus. So it has been designed with I2C in mind. The voltage levels are, of course, compatible. There is a clock, SCL, and the data line, SDA. It supports open drain output, which is the output mode in I2C, and I will come back to that later because it's very impactant. At logical level, it also uses 7-bit addressing and a set of comparable signaling on the bus, including start, repeated start, stop, hack, and knack. But, of course, a few tricks are needed in order to have these two types of devices working on the same bus, of course. So let's deep dive into the I3C protocol. Well, first we need to talk about the output mode. This is very important. So I2C relies on open drain outputs with pull-ups. So this is a very cheap design where you basically have a pull-up that keeps a weak high logical level, so it keeps the line electrically high. But this is not a strong state. And then it has a transistor that then, when activated, when passing, will pull the line low to the ground. And this pulling effect on the ground is stronger than the pull-up with the resistor. So the implication of that, well, there are two consequences. First, many devices can try to force a particular state on the bus at the same time, and this is not a problem electrically speaking. The consequence of that being that only the lowest bit will be visible. So the device that tries to achieve a one, if another one tries to write a zero on the bus, while the bus will be in the state zero in the end. And the other consequence of that is slower rising times. So at 400 kHz, that's not an issue. There is plenty of time in order for the line to go up again and stabilize. But at higher frequencies, this won't work anymore because of some capacitance effects. So another output mode has to be used. And this mode that has been chosen in the I3C specification is named Push-Pull. And it's basically a symmetrical design with at least two transistors to force a strong state both on the low end and the high end. And when in Push-Pull mode, a bus can run significantly faster. So the I3C bus, its typical frequency, is 12.5 MHz. This is the SDR, simple data rate mode that will use the bus at 12.5 MHz and sample one data per revolution of the clock. So this is the default mode that all I3C devices should support. And then there are faster modes. The frequency itself won't change, but the way we sample data will change and we can achieve much higher bandwidth with the same base frequency. So typically using double data rate, DDR. So the clock is still used, but instead of sampling data, we will sample data on both edges of the clock. And there is also the possibility to use ternary symbol pure, TSP. So pure means pure bus when there are only I3C devices on it. In that case, both signals will be considered data and will track edges rather than levels and knowing that each transition can be a specific state. So it becomes a three-state bus with either the clock transitioned, data line transitioned, or both transitioned. So you can basically carry 50% more symbols while using the same frequency as before. When the controller decides to enter an HDR mode, it must advertise all the devices on the bus. If a device does not support the chosen HDR mode, then it just waits for the controller to exit that mode. There is an HDR exit pattern that doesn't require to be able to understand an HDR message. So it's just four SDA, four transitions with the clock kept low. And then a stop pattern. This is very easy to implement in hardware because it just requires four latches in series. So any I3C device should at least have this fallback mechanism in order to know when the HDR communication ends. When there are I2C devices involved in the bus? Well, first, the I2C devices that can be plugged on an I3C bus are only the subset of devices that do not use clock stretching because the clock in the I3C bus is configured in push-pull mode and not in open drain mode like in the I2C specification. I2C devices should be allowed to stretch the clock. For instance, they are not ready to talk and they can keep the clock state low. That's allowed in an I2C bus, but with I3C this is not possible anymore. And other than that, I2C devices should not be confused by the high-frequency exchanges. So the solution here is to leverage the anti-glitch spike filter that is already existing on the most I2C devices or add one if there is none. So this spike filter will filter out any high level that lasts longer than 15 nanoseconds. But the best frequency in I3C is 12.5 MHz, which is an 18 nanosecond clock period and hence a 40 nanoseconds high period. So such a frequency should be filtered out naturally by these filters. And it's also possible to even improve that filtering by using an asymmetrical clock. So in that case, we'll use a lower than 50% duty cycle in order to keep the low state longer and the high state much shorter than 14 nanoseconds. And with a ratio of 2 against 1, we can have high levels of less than 30, 25 nanoseconds. So when there are I2C devices and I3C devices, we call this a mixed fast bus. And such a bus supports basically 3 clock modes, SDR of course, DDR as well. And instead of calling the ternary symbol mode TSP, it's now named TSL for ternary symbol legacy. So this is the same logic but with a slightly different implementation to match the fact that there are I2C devices on the bus. If an I2C device has no spike filter, it's then a slow bus, a mixed slow bus or a limited bus. And the exchanges will be limited to work at the slowest device speed, which is a huge drawback by the way. The bus management is handled with common command codes, CCCs. So this standardizes very well the bus management and serves all kinds of purposes. It supports broadcast and direct addressing. So the CCCs are always sent to everyone. It uses first the broadcast address because all I3C devices should listen to the entire command. And that's also because you can then target this CCC to one or more targets. So below you have the examples. The last bit of the CCC will indicate if it's a broadcast command or a unicast command. If it's a broadcast command, the payload will come right after and the controller will send a stop bit and that's the end of the CCC. Otherwise, if it's a unicast CCC, then the controller will send a repeated start. Then the address of the target that it wants to reach, the payload for that target, the target should hack this CCC and either it continues with a second and a third and whatever targets it needs to reach or it ends with a stop condition. Here is a list of common codes. So we will see the EnterDAA command codes right after. This is for entering a specific procedure. You can see as well the GetPID CCCs which request the provisional ID of the device. While there are many of them and of course none of them are supported yet in the Linux kernel. But we'll come to that in a moment. In order to work, the I3C bus requires 7-bit addresses, much like the I2C bus by the way. So these addresses must be given dynamically and for that there is a procedure named Dynamic Address Assignment, DAA. The I2C devices, they don't know about this procedure. So they are statically described and they already have their own address. So all I3C devices should get part of the DAA procedure in order to get that identifier, unless they are in a deep sleep state or they are powered down, which is completely okay. Before explaining that procedure, I need to give you some insights about what they know internally. Well, a device has what we call a provisional ID, which is a 48-bit identifier, which contains a manufacturer ID, a part ID, an instance ID and a few more information. It has as well a bus characteristics register and a device characteristics register. The bus characteristics register informs the controller about what it is capable of. Typically, it is the device capable of requesting the mastership of the bus. Does it support SDR? Well, what are its SDR limitations? Because SDR will be supported. Does it support HDR if it has offline capabilities and eventually inbound signaling informations? The device characteristics register is more like a definition of the class that this device belongs to. So all these can be advertised during the DAA procedure. So the controller provides a start condition, then sends a CCC command, which is enter DAA, then generates a repeated start and sends the broadcast address. So H3C targets are supposed to respond to that request and they should all send a hack. They will all send a hack, so the data line must be in open drain mode at this moment. Right after sending the hack, all the targets will provide their PID and then their DCR. Here some kind of arbitration must happen. And this is a natural arbitration due to the fact that the data line is in open drain mode. Because I told you high levels are weak and low levels are strong. So the smallest PID will win the arbitration and the highest PID will lose it. How a device knows that it lost arbitration? Because they are supposed to monitor the bus while sending their PID and DCR. And when the bus is in a different state than what they are actually trying to send, it means that somebody else has won the arbitration and they should stop sending. So that's how my target in the middle will stop sending. The H3C target on the right will continue sending until the controller gets all the information. The controller will respond with a dynamic address and the target should hack that dynamic address. The controller then restarts the procedure by providing a repeated start and again the broadcast address. All the H3C targets which don't yet have an address should respond. So there is on my example only one left. So this target acts the request then send its PID. This time it will work. The controller will respond with another dynamic address and the target will finally hack that address. The controller will keep repeating the procedure until there is no more device without any dynamic address. In that case no device will hack the repeated start and that's the NAC condition that the controller will monitor and this is the end of the DAA procedure. From that point any device can then talk. Either because the controller has reached them with their dynamic address or because they want to provide some asynchronous information and this is the purpose of the IBIs. These are inbound interrupts. So a device can raise an interrupt in two conditions. Either the controller is initiating a start condition. This is the same start condition as in I2C. So SDA low then SCL low. Then the device sends its dynamic address. Or the bus is idle for too long so the device itself will assert SDA low and the controller is supposed to assert SCL low as well to end that start condition. As the start condition is now done the device sends its address. At the end of this address there is a read not write bit. It must set it to 1. 1 means it provides its address on this bit to 1 that means I have an ABI for you. So here again arbitration can happen if there are several devices trying to speak at the same time so when the controller provides the dynamic addresses it's very crucial to know which address is given to whom because the lowest address gets the highest priority. The controller will hack or knock the interrupt and depending on the device on the target the controller might need to read a mandatory byte. So this is something specific to the I3C sub-system. You can carry a byte with your IBI. And then the controller decides either to emit a repeated start in order to do what it intends to do in the first place or emits a stop. The hot-joint procedure is very similar to the IBI 1. The difference being at that point when a device joins the bus that has already been initialized it lacks a dynamic address. So it cannot advertise its own dynamic address. The condition on which it will speak will be exactly the same as for the IBI either the controller starts or the bus is idle and the target pulls SDA low and when it is allowed to talk on the bus it will provide a reserved address with the read not write bit set to zero this time. And this means I am requesting a new DAA round in which it will get a dynamic address. Another feature is the controller handover. So this works very similarly again than a hot-joint. So this time the target which requests a master ship of the bus will send its own dynamic address and will end the transaction with a read not write bit set to zero. This means I want to take ownership of the bus. If the current controller does not feel ready it may knock the request and otherwise if it agrees it must hack it. Take some precautions such as disabling interrupts enforcing the end of some processing happening disable specific request while it prepares the bus for the handover. It then assets a particular CCC which will proceed the handoff and end this transaction with a stop bit. As soon as the stop bit is sent the arbitration for that controller is lost. So the controller should release the lines and verify that the new controller assets its controllership. Now let me tell you about a few features that have been added in the v1.1 specification. So this is something that was added in the second time that is also not supported in Linux but they bring a lot of interesting features such as time synchronization. So instead of having let's assume a device is a sensor instead of having the samples on demand only you could request an I3C target to send you these samples on a regular basis. So with set X time you could request a particular frequency and the target would be supposed to generate an ABI each time it triggers a sample. So this is interesting because you'll get the samples at a more precise rate and also it's a way of synchronizing different devices on the bus. So if you wire them well you wire several sensors with the same low power clock you could get samples that have been triggered at the exact same time which is much better than having to ask one device to trigger a sample and then another device to trigger a sample and then a third device and in the end you get three or more samples with different time stamps while here with that feature you could have all the samples with the same time stamp. Another feature that is interesting is the reset feature. So as always in the I3C specification the goal is to do inbound as much as possible. So here we want to avoid the need for an extra wire in order to do these resets. So we can use these resets to do two things. Either we want a global reset of the bus so this might be for instance the master the main controller that actually figures out that the bus is stuck. As the bus is currently stuck the I3C controller will send a specific pattern that means please reset your I3C controller so this will only reset the bus controllers and not the chip themselves or otherwise it could also be used as a targeted reset assuming that the controller will properly configure all the targets on the bus so that only one of the targets will do the reset when it is requesting to do so and the resets can either be a full reset of the chip or only a reset of the bus controller. In order to do that there is a specific CCC named RST Act, so reset action This means please do the following when you receive a reset pattern and this configuration keeps being the default until a new reset act CCC is sent So a device may either discard the reset button reset the bus controller or reset the entire chip The reset pattern itself is made of 14 data line transitions with the clock kept low so it's similar than the HDR exit pattern by the way and after that a repeated start or a stop They introduced as well a new mode named HDRBT for bulk transport This is useful when you have to transfer a lot of data So a typical use would be the direct mapping of a SRAM device for instance So it has a lower overhead It has also announced CRC checks so that the sender knows an additional feature that allows the sender to know very rapidly if the receiver has received the right data or not and if not instead of discarding the buffer it has the opportunity of resending the buffer very quickly And there is an additional feature as well that is interesting The clock could be driven by the target in that mode to avoid some kind of round trip time issues as well Finally, after so much effort to keep inbound all the features as much as possible they decided to add more wires in order to improve the overall throughput So they introduced multilane support with either two or four data lines And it's now time to talk about the Linux API So this is a picture of the I3C framework The I3C framework is a dedicated framework There was some kind of discussion when introducing it back in 2018 and the goal was to try to not break any I3C user So instead a new framework got introduced handling I3C drivers, I3C controller drivers So the point was that you could have an I3C device on an I2C bus, on an I3C bus and an I2C target on both buses as well So if you wire an I2C target on an I3C bus it will go through the I3C controller driver the I3C framework and the I3C framework will then leverage the features of the I2C framework in order to get the I2C driver working So here are a few design choices So it was built as a separated framework The subsystem defines of course an I3C bus object a controller structure as well as a set of APIs that must be provided by controller drivers and the I3C master controller ops structure It also provides the API to register a controller a device driver and the APIs The binding between devices and drivers is made with the content of the PAD, the provisional ID So this is made with the manufacturer ID and some instance ID as well The bus itself, so the I3C bus contains the following members It has of course a pointer over the current master because you can have several devices taking the control of the bus So this must be a pointer of course and this may change The I3C bus mode is the SDR or HDR mode that is used There are a few clock limitations regarding the I3C and I2C devices that could be on the bus and it defines as well the head of the list of I3C devices and I2C devices that are on the bus The controller itself, well themselves they must be described with a struct device a generic struct device in order to be registered in the device model They also have an I3C device descriptor which is the internal description of the device possibly as well they can instantiate an I2C adapter when it's needed Of course the controller drivers must register a set of operations that's the controller API They can be secondary controllers We have a Boolean to know if the initialization is done and finally the list head structures as these will be members of the bus list This is the API for a controller There are many different kinds of callbacks Mainly you have bus management callbacks at the beginning I3C devices management callbacks as well CCC transactions some to do private SDR transfers some as well to manage I2C devices and finally IBI's The I3C core will first pass the information provided by the device tree Typically what are the I2C devices on the bus and possibly as well what could be the I3C devices They should not be described by default but they can be if they have device tree properties to advertise or eventually static addresses as well So the core is aware of the static devices that are on the bus, if any It then calls the bus init callback to initialize the controller So that is really the hardware initialization and the bus base setup and then calls doDAA This is the callback that will start the dynamic address assignment procedure The way the controllers will do this procedure is really open The core gives a lot of freedom to the drivers And for each device discovered the controller should call I3C master add I3C dev locked So this will notify the core about a new device After that the core will register all the discovered devices to the device model Now the device interface Of course it has also a struct device init An I3C dev descriptor that describes the device internally and a pointer over the I3C bus in which it has been discovered Writing an I3C device driver is very simple and looks like any other driver in the kernel You have to provide an I3C driver structure that contains basically a probe and a remove as well as an ID table The registration is done with module I3C driver The probe and remove callbacks will receive an I3C device pointer And the binding is done with an I3C device ID table containing a list of manufacturer IDs on part IDs built with the I3C device macro From these drivers doing SDR transfers is also simple and looks like the I2C implementation The function is I3C device do private transfers and the private transfers must just be declared like on the slide The CCCs for now are not available to be used from device drivers but this may change in the future Finally, device drivers may also leverage the power of IBI So in the probe one must call I3C device request IBI in order to allocate a set of slots for this IBI So the way IBIs are handled is a bit specific They are not handled like any IRQ because of these extra bytes and so on So instead the logic is the IBI slots must be allocated in advance So you decide how much slots you need and the payload for each of them So this is the purpose of the device request call and after that when an IBI triggers the controller driver will provide the content of the IBI and the core will put everything in Q So that's why the handler named here IBI handler at the top is a regular helper it doesn't return an IRQ type for instance and this one will be called in a non-atomic context and the second reason besides the fact that there is an additional payload is that in the handler it's very common that you need to do more transactions with the device and this of course cannot be done in an atomic context Once the registration has been done the driver is responsible of enabling and disabling the IBIs when needed The problem with the slots being pre-allocated is a possible overlap if the device driver is not fast enough in dequeuing the IBIs So what's the status now? Things have moved forward since 2018 hardware manufacturers look more and more interested there are now three controller drivers upstream one device driver upstream as well that's an IMU device that supported I think SPI and I2C as well and now it has a 3C support there is an ongoing work to provide a controller handover support but of course there is still a lot of room for improvement as HDR support is not yet mainline because we lack devices for testing it would be nice to have a Linux I3C target interface The features from the 1.1 specification are not implemented yet so the global and directed resets do not exist and the time synchronization neither and finally maybe we would need as well a slash dev interface in order to provide more control to the final users So this is the end of the talk, thanks very much for watching and if you have any questions I should be in the chat Thanks