 Good morning. So my name is Michael Welling and I will be doing a presentation about iSquared C and SPI, both in kernel and user space. So it'll be a presentation about the various interfaces used for those two peripheral interfaces. I'm going to break it down into two separate slide sessions and the first one will be iSquared C. Okay, so for iSquared C we're going to first kind of discover iSquared C if you don't know what it is. We're going to have some example devices. These will tell you what is and isn't the iSquared C. Then we'll go through the protocol a little bit and then we'll start talking about the Linux subsystem, Linux iSquared C drivers, both on the controller and device. I think that the use of the term slave is going to be used in this presentation and there has been some recent controversy over that. So I'll talk about that when we get, especially when we get to SPI. Then I'll talk a little bit about iSquared C devices and stancing them in the device tree and peripheral platform devices. So after that I'll look at the user space tools and then I'll go into a demo. Okay, so iSquared C stands for integrated circuit. It's kind of a mouthful. That's probably why they call it iSquared C. It was first developed by Philips in 1982, usually for sensors. Now it's currently owned by NXP. Synchronous, multi-master, multi-slave, so those terms again kind of loaded. It means that you can have multiple controllers on the bus at any one given time and they can, multiple devices can start the interaction on the bus. Typically though there is a single host controller and then you have multiple peripherals. It's a half duplex protocol so that means you can only communicate in one direction or one direction at a time because there's only a single data line. Open drain means that the lines are only driven down. The passive state is driven with a pull-up resistor, which you'll see here. There's little rps there. And then there's only two wires, pretty straightforward. We have a link for the Wikipedia if you want to get into it a little further. There are a couple of addressing modes. Since the bus is actually communicating the address in the first part of the protocol, you'll see that in the upcoming slides there's a number of bits that are used for the address. Seven is the original spec. Then they moved on and added a higher number of addressing bits further on in the spec but usually don't use that and usually using seven-bit addressing. There's a couple of versions. The two most common are the 100 kHz which is the original spec in fast mode, which is 400k. There are faster modes, but they aren't used as commonly because most of the interface devices are sensors low data rate. Then IBM, or not IBM, Intel came up with the SM bus, which is a subset of iSquad C, which is used for motherboard control and sensors. It has a little bit of stricter tolerances on the voltage levels and timing and it has optional software level address resolution protocol. We won't get into that much, but it's pretty much iSquad C as well. If you see that term, you can use the iSquad C drivers on that and iSquad C devices. Here are some example devices that you'd see out in the world. We have real-time clock, eProm, converters, sensors. Microcontrollers are a special case. You can make the microcontroller either a host or a peripheral, so that kind of makes them so that they can do either. Touchscreen controllers, GPIO controllers, there's a lot of different things. Here's some example hardware for the electronics nerd in the crowd. This is an accelerometer gyroscope IMU. This is actually on the hardware that I'm going to do the demos on. I got the hardware here. That's the bacon bitscape which I designed. You'll see that there's pull-up lines on the iSquad C and that's because of the open drain protocol. This other guy here is a servo controller. It was on a robotics mezzanine that I created, but then again you see those pull-ups. This is usually shared amongst a bunch of devices, but you don't need the pull-ups on each one. You just need it one time on the bus and it's usually 4.7K. It can go lower if you have a higher speed bus. Here we're going to look at the iSquad C protocol. The important thing about this protocol is understanding how the transaction starts, the addressing, and then read, write, and then the data that comes across. There's acknowledgement bits, and the acknowledgement bits will tell you to the bus, okay, the device is there. You can start talking to it or it can start talking to you depending on whether you set the read or write bit. When the bus starts the transaction, it'll lower the SDA and SEL in a very specific sequence. You can see that the blue highlighted area here, that the SDA will transition low followed by the SEL transitioning low, and that signifies the start of transmission. Then there's a signature protocol, so you'll see the address lines come through, and then you'll have a clock for each address bit. It'll clock, and then you'll read, write, which will tell the device you're talking to, whether it wants to read or write, and an acknowledgement bit. If the acknowledgement bit is set, then it will send the data byte that you want to send across or bytes. It can be multiple bytes, usually it is. A lot of the times there's an internal register address as well as the device address, which is easy to confuse. The first address is the device address on the bus, and then there will be internal register addresses that will go into the data lines here, and then you'll have the read and write data as further down in the transaction. That's pretty much how the iSquared-C transaction looks. Let's get into the iSquared-C subsystem a little bit. I like to do a little history, but I don't want to even guess at the pronunciation of the name, so I'm just going to let you guys read. I have a tendency to murder people's names, but there's not been a whole lot of changes in terms of internal structure since the development of the device model. It's a pretty steady or stable subsystem. Here's a block diagram of the subsystem, which gives you an idea of how everything works internally. iSquared-C core is the center of it all, and the core driver talks to the bus drivers, which will talk to the client drivers, and that will be what communicates with the user space. The algorithm, which is part of what communicates down to the hardware and the adapter will be used together to implement the controller-specific device driver. The majority of the work for a user will happen in the client drivers, but I will cover bus drivers as well briefly. If you're going to start doing iSquared-C development at the level of host drivers, then you'll want to probably join the mailing list. This is fun to watch sometimes, but this is one of those core subsystems that's used by a lot of other subsystems, so there's a lot of activity on the other subsystems. This one only happens when you add a new host driver, and I could imagine there could be more activity on this as new, like, RISC-5 implementations and their iSquared-C controller. Same goes for SBI. All right, so the bus drivers, so this is the controller side, so this is what is register mapped to the main SOC or system on chip, and that's going to help drive the lines to the bus. There's an algorithm which contains general code for the class of adapters, and then the adapter is kind of one driver for that, and if you want to get a little more information, this is actually grafted from the iSquared-C documentation, and I might actually show you a driver so you can kind of get a solid understanding that this is just parts of the host driver, the bus driver. So first thing you want to do if you're creating a bus driver is you define and allocate private data with this iSquared-C adapter struct, and that struct has fields that you want to fill out while you're initializing the device. There's an algorithm struct as well, so that will tell you, okay, these are the callback functions that will be used to drive the transfer with the hardware, and then the adapter will have the algorithm and the private data for the algorithm, and then the iSquared-C set adapter data will be used for that, and then once you have everything filled into that struct, you add the adapter. There are some more details, but if you are interested in this, there's probably a bit more documentation out in the kernel docs. All right, so then once you have that core driver, you need to connect devices to the external, what are called device drivers. The documentation, actually, this is copied straight from the documentation of driver-driver. Yeah, I didn't come up with it, so it's just a way of saying, okay, you have each tech device gets its own data in the client structure, and I think this will be more clear as we go on through the next set of slides. Okay, so here's kind of the basic starting point for device drivers, so you have this iSquared-C struct and the other driver and OF match table. Now, this is going to be for a device tree, so you'll have a table that lists all the compatible devices that will be attached to the driver. ID table, which is another table for matching. You have a probe function and a remove function. There are other fields in this struct, but these are the basics. Okay, so you'll see I have an ID table here, so this is just a very basic example, so you can see how it works, hence the foobar stuff. But yes, when you want to add the module device table, you just do that, and then if you want to add a device tree compatible string for your specific device, you can do this second one here, and they both use the module device table macro. Okay, I don't know if this is going to be too small to read, but this is the example probe function. It's not a very good one, but it just shows you that that's what the function pointer that you passed to the struct, and then you can do some basic initialization on the device. That's the I squared C SM bus read data. It's kind of like one of the several internal APIs that you can use to communicate to the device. Once you have that, then you also have a remove function, and this may or may not be needed. So the DevM instructions for the allocation, it will automatically clean up. So this may or may not be necessary depending on your driver, how much data you allocate. I usually use free memory in this function and close down any device clean up, essentially. All right, so each client structure has a special field that can point to anything, essentially, and this is used for the specifics of the device itself. Say if it's a touchscreen controller, you could store the values of the last read sample from the device so you can say, okay, this was the last XY coordinates and that can be stored. It's just the internal state, essentially, and these are used to retrieve that data or store them. Okay, for initialization, there is a special macro that they use for I squared C, specifically, which I would recommend. It's the one on the bottom here, but really it's essentially wrapping these top functions in generating them, essentially. So I squared C add driver will add your driver and register it with a system. I squared C delete driver will remove it from the system, but you'll notice that it's just a macro template and the I squared C driver is pretty much the one to use for most cases. Okay, so the internal API is used to communicate to the device, so when you call these functions, it'll call back to the I squared C core and the core will call down into the host driver for that specific SOC that you're on. So it'll send or receive data and these are the basic I squared C send and receive and I squared C transfer and that's just to send or receive a combination or multiple messages or sequences of bytes. These are not as used as commonly as the SM bus ones, so there's a set of them and they're all kind of listed here. I'm not going to go into each one because they're very kind of what you see is what you get. They're very self descriptive. A lot of times you'll be using the byte functions, but sometimes you want to do a whole block of data. It really depends on the device you're talking to and there's another thing called the RegMap, which I won't go into too much, which kind of abstracts the bus a little bit. Okay, so there is also an interface in Linux that I won't get into too much, but essentially what you can do is you can have the Linux device emulate a peripheral and that way another host can actually talk to it. It's commonly used for testing or basic communication between two devices. I've never really used this. I haven't found a use case for it yet, but I think you can do some pretty neat things with it. Okay, so instantiating or binding to the driver is required so that it will actually probe the driver when you instantiate them. So this is an example device tree, which I don't know if there's another session on device tree, but it is kind of a hardware description that's used to associate the hardware with device drivers. And you'll see that this is an I2C1 bus. This will depend on the host controller that you have. There's a device, a physical address that is the register mapped address of the SoC, and then you have individual devices on the bus. These are the peripheral drivers, and those will tell you kind of specifics about the device. Okay, we have an ML flash and an NXP GPIO controller on the bus. The register is actually the address, the bus address of the specific device. So this has to match, otherwise it will not attach to the device. Okay, and in the old days, they used to have this thing called platform devices. This is how it used to happen when I first started working on Linux. It's really just a C file that had the registrations. It became unmanageable and was changed eventually. But yeah, for historical reasons that's here. Okay, and you can also do binding through the user space. So if I wanted to attach to a specific bus, I can do that using the Sisyphus interface. And that can be useful in devices where it's, say you have an external kind of universal connector, you want to plug something in, and then you want to attach the device to it. The driver will be bombed through this string that's passed into the Sisyphus layer for the bus. Now, there is kind of a generic I2C bus device, which is exposed to the user space, and this is used for under development or kind of user space drivers. You'll have a node at slash dev I2C X with zero through whatever, depending on the number of buses on your device. You have a slave address, so there's an I2C control for setting the slave address. You have read and write functions. And then those are the standard read and write functions. So when you open the device, use the I2C control to set the address. You can start doing read and write, just like you would with a normal file. But there are I2C wrapper functions in user space as well. And there's also Python bindings for doing this as well. Then you have, there's a utility, which I'll demonstrate, which does I2C tools, which can detect devices on the bus, and you can read and write from the devices on the bus using get and set. And these are useful during early development before you have a device driver ready or for kind of generic encapsulation of the bus. Adafruit, I think it's called Gemma, there's a, their MicroPython wrapper for Linux essentially abstracts the bus into their libraries so that they can reuse libraries that they created for the microcontroller stuff. And it's kind of a clever way of doing it, it also is not the Linux way of doing it, but I guess both ways are. All right, so here's an idea of what you would see when you do the LS in the dev directory for the I2C devices. You have multiple devices on this particular board. This is the Pocket Beagle, which I will be using for the demos. And you'll notice that it has, there's a device node for each. All right, and then the I2C detect. So what that'll do is it'll send out a read on each of the bus addresses, and then if a device responds, it'll actually put the device hexadecimal address printed out. So if there were more than one devices on the bus, it would print them in the different slots. There's one of the inherent difficulties with I2C is two devices can actually have the same address and cause bus contention. So you have to watch out for that. You can't put the same device on the bus multiple times unless the device has a way to pinstrap different addresses essentially. And you don't necessarily have to have the same exact device to have the same exact address, so you've got to watch out for that as well. And this utility will help you when you're first debugging something like that. Okay, so this example shows you kind of an idea of how to dump registered contents from a specific device using the Escort C Dump. Now, this specific device, the MMA is the accelerometer that we have on the device, and it's kind of hexadecimal encoded, usually not really useful to anyone without datasheet, but that's what you get when you do the dump, and this is useful if you're doing, you know, basic testing early on. Getting set are individual byte read and write functions, so these can be used to kind of poke at the various registers within the device. The MMA device, we were probably enabling the conversion and then reading back registers to see if it was working. Okay, so that is the Escort C section, so we're going to go into the demo. A lot of the demo is kind of in the slides, but we can look at it live on a target, so let's switch over to the screen. Okay, so this is the, can you guys, I'm sure you can see it, it's a sharing. Let me see, we got a question here before I start. I heard about Escort C clock stretching. Yeah, we got a couple of questions here. This screen is too small. Yeah, I'd have to go kind of this clock stretching question. I would have to research that a little bit more. It's been a while since I've looked into it. Don't remember off the top of my head. Another question we have, what can we use Escort C utility to test Escort C back mode? There, you would only be able to do that, say you wanted to communicate back to, from one host to another, it doesn't work. So loop back really wouldn't work unless you put, if you had the capability and you drove the device into a peripheral or slave mode, and then you could loop it back, but for this, the easiest way to do testing on the bus to make sure that it's working is to go ahead and plug in pretty much any device and probe the bus to see that you see the address. A loop back isn't particularly useful. Escort C detect, we have another question about that. So Escort C detect just goes out to the bus and helps the individual addresses and determines whether there's a device at that specific address and prints it out. So let's show that on here. Escort C detect. Okay, so if you don't add the parameters, of course it'll tell you you need to do this. So I usually use dash R and dash Y and then the bus. And you can specify the first and last address that you want to probe. Zero means the zero with bus. Of course I have to put the Escort C detect here. And what that does is it reads the bus and for every device that it sees the response to an addressing, it'll print it out. It's not the nicest thing to do to a bus. Sometimes it can cause grief with some external peripherals. And it warns you about it. But on most embedded systems it's not a problem. So then you can go to the different buses. We have three buses on this guy, zero through two. And you have one C here. That means that this device is responding and it's available. And that is on the particular hardware that's the accelerometer, which is this little tiny chip on here. But that's how detect is used and what it's used for. For I squared C get, we had a question how to read 16 bytes data and you can just change the mode for that. A word is 16 bytes. So that's just part of the utility. So say we have, we wanted to do a word write. You just put W instead of B here. That one's pretty straightforward. The Python binding for, I remember we did a demo with it a couple years back. Let me go ahead and get you a link for that. This pretty much encapsulates the I squared C dev. So there's one called SM bus two, which is a Python dash SM bus two, which you can use. And where's the chat window on this thing here. That window that comes out to everybody. Okay. So the method of changing the I squared C bus speed and that's part of the driver essentially. There is a way to do it. Okay. And the demo and that's kind of what we're doing now. So that's the last question. So if we do, let's go back to our slides about, so I can remember the commands. Let's do a look at the I squared C registers. From that accelerometer. This is the range of registers you want to look at. Bus number. This is the bus device address. So it looks like we have something similar to what we had previously. And notice that the first bite is different. And that's, it's probably not enabled yet. So go over here. Let's try doing these commands to set the, it's going to read a register. I forgot to put something here. Oh, the bus. So why says to go ahead and do it? Even if it's a warning. How to use the bus. The one, one C is the address. And D is the, is offset register. Okay. So we have a three A here, or two A instead of a three A. Let's try looking at another register. Like it shows in the demo. Okay, we got a zero there. Let's do a set two A. So this is the, the two A is the value. And one is, or two A is the offset and one is the value. So this is probably enabling the accelerometer. We'll read back that register and it's set. Now if we do the jump again, we'll start seeing data being presented in the first couple of words. So we wanted to read the word. We would say, why bus to address zero one Charlie. And the data address, which is zero. And the mode, which is word. And that gives you the value of the first two bytes. That changes when I, when I move around the price, and notice that it changes the value of the, of that first byte. That means that the device is working. All right. So I'd have to go into the data sheet to figure out exactly which is X, Y and Z. What exactly is going on with each one of those. We won't do that for this demo. See if we have any other questions here. How does. Yeah, how does arbitration work for multiple masters on the same bus. It's essentially the Wild West. You have to meet your own protocol. Usually you can coordinate that through the bus, but it's, it's kind of tricky. It's, it's, I don't think there's a specific protocol for it. And most cases you don't actually want multiple hosts on the same bus, but sometimes you might want like a side channel to the bus. It's not going to, it's not going to damage the hardware. It's just going to follow transactions that happen at the same time because it's open collector. It's never going to contend on the bus. But yeah, arbitration is, is kind of tricky. Because there is no, I don't think there's a protocol for it. And the next question is, as you said, the simplest way of testing I squared C is the plug in device. Are you plugging a square device into the bus of the Linux system? So yeah, typically you'll have a bunch of pins that come off of the SLC and their pin mocks is some multiplexing essentially multiple functions can do us to a single pin. What you'll do is you'll set the multiplexers so that they are in I squared C mode, enable the host controller, and then attach to those pins with the device. Now, the host controller has to be there on those pins. It can just be any set of pins. But once you've done that, then you can plug into them. Typically if there's expansion ports or a header that will have like a pin spec for it and they'll have the list of pin functions, then you can select I squared C function and attach to those two pins. So that's kind of how that works. The next question is from I squared C detect output. I see the device starts at three. This is correct. I think the early addresses are just never used in spec. My guess is there's probably a good reason for this. If you consider the address, maybe the bus is stuck at a specific value and it might look like a transaction when it's not, I don't know. This is kind of, you'd have to ask somebody back in the 80s when they designed the protocol or dig into the documentation a little bit, which is fairly difficult to find. Since we have about two minutes left on the first presentation, let's kind of go back to the first clock stretching thing. Oh, it looks like we had another question about SM bus clocks. Yeah, the clock's stretching. So it's just a way of slowing down or allowing the controller time to react to the previous, like when you first address the device, it might need time to think about what you just said. So it might, since the clock speeds so fast, it might need a little more time so it slows down the clock essentially. That's what it looks like. But yeah, I'll look into that for the next time I give this presentation, which has been quite a few times. All right, so let's go ahead and switch to off-screen share and to the next set of slides, which is SPI. Okay, so like with iSquad C, we have kind of what SPI is. Example device is different modes. We have the subsystem, device drivers, instantiating or attaching to the devices. The user race tools and demo if we have time. Okay, so SPI stands for serial peripheral interface and it's a full-duplex synchronous serial. We call it master slave but there's been some recent controversy about this because it's of the casual slavery mentions. So they might start renaming things and data sheets. We'll see and I'll update this as those come in. So it's a de facto standard so it's not really a standard developed in 1980s again by Motorola. So each bus consists of a single master or host device and multiple slaves or peripherals. It's probably the new preferred language. And then there's a set of IOLines that are used to communicate to devices on the bus. There's a clock line, usually denoted as clock or SCK. There's the mice line, master and slave out. I think that the new terminology is controller and peripheral. I don't remember. This is sometimes called SDA. There's a bunch of different ways of annotating it, but mice is the most clear. Then you have MOSI, which means master out, slave in. So those are just single direction lines and you can see by the little picture over here how that kind of works. And then there's a chip select. It says, oh, I want to talk to this specific device. There's no addressing over the bus. It's just a line that toggles. And sometimes you'll have an IRQ. This is particularly useful for input devices or devices that need to trigger events. There are also cases where you'll need to register GPIO to enable specific functionality. Like with a TFT, spy TFT, sometimes you'll need to toggle an IO line. It's not SPI specific, but sometimes you'll have to bind that with the driver. So here's kind of a top level view of what it would look like on the hardware side. So you have the host controller and then the peripherals on the right-hand side. Each one has its own chip select and the master out goes to the slave in. So that's kind of how it works. And here's a few examples of devices that you would see out in the real world. So analog converters are very common. Use case sensors are also very common. So these are, you're going to see a lot of that with your IoT devices, inertial or temperature pressure, humidity. There's a sensor for everything. And the lower bandwidth ones usually use iSquared C. And then, you know, you can do, or higher speed ones will go SPI. And you can use, there's some that actually use both, which complicates the hardware a little bit, but yeah. Touch screen controllers, FPGA programming, the iS40 FPGA actually has an SPI bus that's used to program it. It's an interesting use case. Over here we have the hardware that's on this device, which is, we're emulating a seven segment LED with a GPIO controller. So, okay. So there are a few modes for SPIs, which determine the way that the clock and data lines interact. And the two parameters are polarity and phase. So C pull and C phase are phase. And for each device they'll have a specific mode, which will tell you how it's going to react. And this C pull and clock polarity, if it's set to zero, that means the clock will idle low. And if it's set to one, it'll idle high. And that means just outside of the chip select window. We'll show you examples of this. And the phase is just determines what edge of the clock that the data is latched on and transitioned. So what happens is it uses both edges of the clock for SPI, and I'll explain that with the timing diagrams. But then the modes are represented by a tuple and or the encoded form. Okay. So let's go through the different modes. If we set the polarity to zero, that means that the rising edge is used to latch the data, and the trailing edge is used to transition. So the data is latched on the rising edge. But if you set the pole to one, it does idle slow still, but then the data is latched on the falling edge. So the falling edge of the clock is this transition from one to zero or high to low. And you'll notice it's in the middle of the moseye. So that just tells you how the data is transferred, how it's shifted along. Okay. And then for polarity one, you'll notice that the clock here is on its settling high outside of the chip select window. This chip select window is this here on the bottom. So that's essentially enabling the device. And then the clock transitions are opposite. So you'll see on the trailing edge or the falling edge, it'll latch and it'll transition on the rising edge. And then if it's mode three, which means that both the pole and the phaser are set, this is kind of the timing diagram for that. And you'll notice that it latches on the rising edge here. So it really depends on the device you're talking to, which mode it uses, and it will typically tell you in the datasheet. But if it doesn't, you can look at the timing diagrams and try to match them up. And some devices will work with multiple modes. It's just a matter of figuring out what works best. Okay. So let's go into the Linux subsystem a little bit here. It was, again, developed in the early 2000s. In the transition from 25 to 6, there are a lot of developers that were involved. Only a few maintainers have held the subsystem so far. Mark Brown is the current maintainer. I think his nickname is Bruni. Okay. Here's the mailing list. So if you're wanting to write a device driver for a host controller, you would go onto this list and submit patches. If you wanted to submit a driver for a specific peripheral, you would figure out which subsystem the peripheral resides in and then send it to that mailing list. And it's usually, it's all over the place. There's so many different subsystems that use these core drivers. So we'll talk a little bit about controller drivers, which are the host side. So they kind of abstract and drive transactions. And there's an SPI core, just like there was an iSquad C core that it communicates to. Then you have callbacks that are used by the core driver to drive the transactions. There is a struct SPI controller, which is a fairly large struct that is used with all the different parameters and callback functions. Okay. So here's kind of the basic flow for creating a host driver. So the first thing you want to do is allocate the master with the SPI allocate master function. And then you go ahead and set the controller fields. There are a bunch of different flags, a bit modes. Then you have the callback functions for setup and cleanup. Then you can choose whether you want to use transfer one message or transfer one callback. And the implementation really depends on the host driver, which is easier. I think the transfer one message is used more commonly. And then you register the controller using SPI register master. We'll talk a little bit about device tree binding. So for each device that you want to add to a bus, you'll have a chip select that's associated with that. And those can either be the native chip selects, which are defined by the host, or in some cases you can use GPIO chip selects to override and use. And if you want to use a mix of the two, you can actually use, if you put zero in that field, that determines that chip select one, zero one, is actually using the native chip select. And this implementation of this is different for each board, but GPIO chip selects are useful for when you can't have, either you can't access the native line because it's covered by another function, or you want to have more chip selects on your bus. Okay. So here's an example of device tree binding for a host controller. And this is the TI one. You'll notice that it has the register map. Each device will have a different address here. Not just the memory or the memory mapped IO space where that controller is residing is very specific. Then you have the number of chip selects. You typically have to pass DMA channels for the device so that it doesn't bog down the system when you're doing transactions, long transactions. And that kind of just automatically kind of happens in the background. You got your status equals disabled. What that means is the device is going to be disabled by default. And they do this on the top level or the DTSI files. So the top level will include this DTSI file. And then if you want to use that specific peripheral, you'll set the status to OK. And then you'll attach devices to it. And we'll show you that further down in the presentation. All right. So protocol drivers are the drivers that most people are interested in because that's when you're interfacing with the device external. So for each, when you intend on accessing, you have a protocol driver and they are spread out through multiple subsystems as explained earlier. The common ones are IIO or industrial IO, input MTD, just flash. Messages and transfers are used to communicate to devices and are kind of directed through the controller, through the SBI core to the device. So this kind of happens in the background. And they abstracted it so that it makes it a little easier for device development by strivers. There's a spy device struct that has passed probe and remove functions. And this is kind of where the magic happens. All right. So there's kind of two terms for the messages or transfers, kind of sequences of bytes that are going to and from the devices. So transfer is a single operation. And there are TX and RX buffers because it's a full duplex. You need to have buffers for both. A lot of devices won't communicate at the same time on the TX or the MISO and MOSI, but it's something that can happen. So you have to have two buffers. The chip select behavior. Sometimes you don't want the chip select to toggle between transfers. So you have to set certain flags and you can set delays after the chip select or there's a lot of different parameters that you can set depending on the needs of the device. And then there's messages, which is just a sequence of transfers. And usually this is what you use for your driver. Okay. So we're going to look at this by device struct. So there's a lot of different fields in here. There's always advice. Then you have the controller and master various parameters for that specific bus or device. And you know, the controller state, a little different fields in there. Okay. And here's, if you look back here, you have some, the mode can be set with these macros. So this is how you set the phase and polarity. And you see that they have the chip select modes, which are combinations of those to the byte ordering, whether you want to do multiple data lines out. And this is a special use case, a quad and dual spy. Quad spy is pretty common. Dual spy, not so much. Okay. So in your probe function, you have your own chips or you're on struct, which contains the data you need for your driver. And then your platform data. And then we have an open firmware device match. So when the probe happens, this open firmware device matches used to attach to a specific device or binding. And then you have an allocation for the driver state typically. And you can set that into your, using your set drive data and set that into there. And then any functions that are used in the driver and you need that data, you would use the get function. Okay. And then you'd have kind of an open firmware match table. So this is the compatible string for your device. And sometimes you'll need data depending on the vice. This is usually, it's particularly useful for devices that are device drivers that control multiple different types of. ICs. So you would have, say you had an NADC driver, but the core of that driver is common amongst all, like maybe five or six different devices. And you can use the same driver. And this data can be used to distinguish between them and change the behavior subtly depending on the specific chip that you're using. And I can probably show you that with industrial. I always probably got a good example. Then you have the spy table, which is registered. And you have the driver struct. And this is where you pass the functions pointers. So the probe, remove, shut down. And I'll have an example of that, the ID table. And the vice driver. Okay. So here's an example of that. You would pass the name. PM is power management. And this is optional. But if sometimes when you go into low power modes, you want to change the state of the device to put it into its own low power mode, then this is useful. The open firmware table is just a pointer back to that table, which determines what devices can be attached to this driver. You have the probe. Probe function is important. And then you have the ID table, which is kind of like a spices specific ID table. And it's used for different type binding. Okay. So, well, let's get into the kernel APIs that are used in your driver. And these are the basic ones. Spy async, spy sync, spy read and spy write. And these are essentially used to send messages to and from the device or simultaneously, since it's full duplex. And async is can only be issued or can be issued in any context. And this is synchronous. One can only happen in an on sleep or an on sleep context. Not an IRQ. So, or in a sleep context, which means that it can happen in an IRQ context. So, and it makes spy drivers a little tricky sometimes. And the thing is that those transactions take too long to happen in an IRQ context. So, you really don't want to do that anyways. So, you have a bottom half to handle that essentially. And then the spy read and write are pretty self-explanatory, but they're kind of wrapper functions. There's some more specific ones. Spy flash read or read flash is for SPI flash commands. This is usually all encapsulated in the MTD layer. So, you could probably find examples of that there. Spy message init. So, this initializes a message and then it's just a struct that it fills in. And then you have the transfers. So, you add transfers or messages to the transfer using this function. I think there's an example of this somewhere further down on the slides. Okay. So, the binding or instantiating devices, you can use a bunch of different modes or methods, the device trees most commonly used. So, for each node, there is, so like every controller has a series of chip selects and that underneath that, you can set the different devices that are attached to those chip selects. The compatible string will tell you which driver it attaches to through that firmware binding or the interface. Register is kind of the chip select number. So, it'll determine which one of the controller chip selects to use. And then the maximum frequency is kind of like an upper bound on the frequency for that specific device. And this is really determined by the device. So, you want to set an upper bound so that the controller doesn't go over that. The controller is never going to hit that exact frequency though because it's using a pre-scaler. So, it'll be something lower than that maximum frequency that you can set it to. Okay. And these are some different like bit fields or flags that you can set in the device tree to set the polarity, the phase, the chip select high. This is an odd use case because it really kind of messes up the bus. But sometimes there are devices that want the chip select to go high instead of low. And that flag can be used to set that. You can't have other devices that are chip select low. There's ways around it, but it complicates things. And then you can set the bus width. You always are typically one by default. And then you have the delays. And those are all kind of corresponding with the information from earlier in the host. Okay. So, here's an example of instantiating a node. So, you have underneath the spy one here. So, the spy one is defined previously. You're adding the, you're adding the status okay. You're setting the pin control. So, this does changes the multiplexing so that those spin or spy pins are connected properly. And then you have your spy max frequency and your different parameters. And then you have register zero, which means chip select zero. And you also have the at zero, which is kind of like a designation. All right. So, platform registration is the old way of doing it. So, there's that an example. It's pretty straightforward. Not that you will be doing this pretty much any modern system. All right. So, let's let's look at the user space utilities. Spy dev is essentially a universal driver that abstracts the core driver or the host driver. And it allows you to connect it pretty much any device from user space and make kind of user space drivers. So you pass data, you have buffers and you hand it off to the host driver and it does its magic. And then it'll turn it over turn back from from kernel space and to user with the results of the transaction. Okay. So, we like to kind of give you the do's and don'ts with spy dev because this particularly kind of sticking point is like the the maintainer of the spy subsystem doesn't encourage the use of this, but a lot of hobbyists will and will use it and kind of give you the use cases where it's useful and should be used and then the ones where it shouldn't be prototyping underlined. This is a good time to use spy dev so you can figure things out. Yeah, it's just a good time like to do that and then simple protocols or talking to a microcontrollers something that you would never end up putting as a mainline driver, but you could also use an auditory driver or module. It really depends on what you need. But if it's a very simple protocol and it doesn't need any interrupts or anything fancy then you could probably end up you can probably use spy dev. And when you shouldn't use it, of course, if there's a driver, don't don't use spy dev use the driver. If you have to write a driver and you're dragging your feet, just write the driver, especially if it needs to do IRQ handling. It just it makes more sense. Things that aren't accessible to user space easily like interrupts. Yeah, you want to do this in the kernel, not using spy dev. Okay, so spy dev has a set of functions using IO control and read and write access is possible but you usually end up using IO control for the transfer as well. Here's a list of headers that are required. The most important one is the Linux slash spy slash by dev.h, which will give you the list of IO controls. Okay, and the sysfs node for spy will include the spy device child nodes. And for each spy chip select, you'll have a dev entry. You'll have a sysfs devices entry, and you'll have a sys class spy dev entry if it's bound to spy dev. So those are the different user accessible interfaces. The important one is the character device, which is what you'll be communicating with with like a standard C program or wrapper library. Okay. So the opening and close are just like normal. You open the device and then you can do read and write, but they're half duplex and you can't do crazy transactions that require full duplex. Then there's a IOC message, spy IOC message IO control that's used for that. I think I have an example of this. Okay. So we're going to go through the different IO controls that you can use. So you can set the modes, read and write mode, depending on. So if you need to set the polarity or the phase dynamically, you can do that with these. And then you can change the LSB orientation and the read bits and write bits per word. This is usually eight, but it can be more. In the max speed, you can set that as well. And this allows you to kind of make like modify parameters in the kernel using the IO control to set up the controller. Okay. So here's the basic idea. You have a couple of buffers. And those buffers are pointed to by a transfer struct. And then you have a delay and a length that'll change depending on how many bytes you want to send. Then you open the device and then you perform this spy IOC message. I'm glazing over like any modification of the phase and polarity, but this is the basic operation. I have a test program that I created a while back, which is linked below, which kind of allows you to, I think, send a message across the spy dev interface. And this is particularly useful for testing. All right. So that gets us to demo slash question area. And it looks like we have plenty of time. See if we have any new questions. It's like nothing new with the SPI. So let's switch over to the screen here. So this particular device probably looks like I have the wrong SD card in here. Let's look at these. If we have the spy dev nodes are not CD. So it looks like we have spy dev 01, spy dev 10. So we have two devices registered on the system. And so if we went into the both devices here, back to the platform. And this will give you some information about the device. If I have to plug in here, though, this might make the demo difficult. Typically, we just demonstrate the LEDs on the board using the spy interface. Don't have the right SD card in there. I want to do this and see. So instead of going into live demo mode, we'll look at the drivers. Okay. So as stated earlier, there is a spy system. Okay. And in this directory, you have all the different host controller drivers. Most commonly used. Part of this is the spy.c, which is the core. And then the rest is kind of like specifics for the controller that you're using. There's the spy dev interface. Let's look at the OMAP driver, which is the one we have here. And there's going to be a lot of like controller specifics in this. This is your right. The driver as well, apparently. Here's an example of the platform driver registration. Here's an example of retrieving the device beta, the dev beta. Here you see it's filling in all of the parameters for the specific device callback functions. And you can see that these are some of the more, this is setting for the Kandy MA transfer one. This is the transfer function callback. It does the specifics for that controller. That's really a simple example, but you get an idea. You can set the frequencies, min and max, and message, setup function. Use to set up the transfer. Not really exciting, but someday you may need to implement that. But sometime you have a device driver depending on the subsystem. I think for this specific device, we're using a GPIO controller. So these are the different GPIO controllers that are available. And it has a core just like the other two subsystems. Let's take care. MCP 23. It must have moved it. It moved into pin control. Those are the device driver for this controller that's on the board. I don't know if I have that. Of course, this is one of those drivers where you have both I-squared CNSPI. It's more complicated. But then there's a spy in it. And then you have a spy registered device, as mentioned earlier, or pass the pointer to the spy driver struct, which has the different functions structs pointed to. So this is the probe function. And you'll notice here, you have an open firmware match. The platform data fills out the platform data. Then it parses some specific flags for the driver from the device tree here. And hence the per instance allocation sets the driver data. It does the probe for the individual devices, the different IDs. So this is different devices that can be attached. So that's kind of like a basic SPI driver. I guess I'll just open it up for questions now because I kind of botched my demo and forgot the SD card. I was able to pull down my demo from GitHub and use it. But I don't know that I have the right registration here for IDEV. These messages, just the kernel messages for the board. See if it's got any SPI devices registered here. I guess there's no other way to do it. It's just a CD. And here's the drivers. I know why this is set up for a silly demo to run DOOM on the Pocket Beagle. And so it's got a SPI TFT. And that's what it's rigged for. It doesn't seem like a very good demo for this, though. So let's just go back to the slides and see if we have any questions. I've got three pages. So we got a question from Keyrock Lee. How do you know the offset? And I'm guessing that is the offset of the iSquared C transaction. That depends on the datasheet. So the register offset will be described in the datasheet. And the UU in the detect means that there's a device that's already, or a driver that's already attached to it. Where do I get the 00 through 31? So that is just a list of register addresses. It's just all of them, essentially. Can you give some quick pointers on how to leverage the interrupt pin on a slave device? So when you create the driver, you'll register an interrupt in the probe function. And let's see if we can find a good example of that. This is the interrupt callback. During the setup, we can register an IRQ. There are different ways to do this, but this is one of them, a threaded IRQ. And the IRQ, here we go. So you're reading, essentially what this is doing in this specific example is it's a chain IRQ or a threaded IRQ. So the GPIOs can actually trigger, all of them can trigger an IRQ, essentially. So it's a little more complicated than you usually have. But then there's an IRQ that goes back to the main device. And I'm guessing, I don't know, this has probably happened during the icecrits-y portion of it, but it's kind of the same thing you, let's see if we have a better example here. This one, luckily it is a spy device. But you'll register an IRQ. That's not a very good example. Yes, there's the same thing, the request-threaded IRQ here, driver one-shot. So you pass the IRQ through the registration, and it passes it to the core, and then the core passes it to the driver. So we have just a little bit of time left, so I'm going to try and bang out all these questions. So user space command and change bus frequencies. I don't know, I don't think so off the top of my head. I think it's a configuration thing in the binding. It's really controller specific, whether it can do it or not. Looks like we had another question about IRQ, which kind of covered. What situation do the device support both? Okay, so device driver can support both, or device can support both. And ones that I've seen, like I've seen a touchscreen controller, driver that has both I-squared C and SPI versions, but the core functionality is nearly identical. Sometimes the device can be pin-strapped into either mode, depending on the levels on certain lines it'll put into certain modes. I've seen that with audio codecs. So there's a bunch of different situations where both are supported, either on hardware or in the software side. Is there a reason for choosing different modes? And this is the controller or the device specific. So if there's two devices on the bus and one uses a slightly different mode, you have to be able to set it. So you can't just use that one for everything. There are some cases where the modes have to be different essentially. There is SPI slave mode for Linux, I believe, but it is less used and it's not all that commonly used, but it is available. The controller tree binding of SPI, only master details, presence, but without advice for the use of it. So when you register the host, it's kind of on its own, and then you override it with the top-level DTS file, which attaches the devices as slaves. There's a lot of questions. I probably should have looked at these before. The SPI Dev is a loadable module. Yes. Does SPI slave device talk with user space driver? What's the difference between slave driver and SPI Dev driver? So SPI Dev is a generic slave driver, essentially. And it doesn't encode any of the protocol. It just passes through the protocol from user space. We have another question. Is it possible to connect FPGA and Beaglebone Blue using SPI? It should be. I designed a board called Beaglewire, which is an FPGA cape for the Beaglebone block. So it says I cannot see your screen and see that earlier. Was the screen share going the whole time? Okay, so maybe somebody had bad feed. And the slides are available on the SCAD, and they're also on the ELC site. So they're all available for the person that was asking for them. That does it for all the questions. Anybody want to throw one last one in? Nope. Looks like it's wrap-up time. Okay, so I guess that's it for today. I hope you enjoyed it. I'm sorry about the demos. I should have double-checked my SD card, and I didn't want to shuffle around and look for it while I was on camera. So that does it. Thank you for joining my presentation.