Writing a Linux SPI driver - Part 1Last Updated on Thursday, 14 April 2011 07:38 A Linux SPI system overview IntroductionWhat follows is a guide for developing a SPI driver for the Gumstix Overos. The steps should translate to any OMAP3 system running Linux.
Contents
The project code can be found at http://github.com/scottellis/spike
You can use the Github 'Switch Branches' menu in the upper left if you just want to browse the the code for a particular section.
OverviewThe kernel documentation for the SPI framework is a good place to start. You can find it in your kernel source documentation directory. Here is a link spi-summary. Linux SPI drivers have two parts. A controller driver that handles direct communication with the hardware and a protocol driver to handle the details for a particular device.
This article will be describing a SPI protocol driver.
The mainline kernel has a generic SPI protocol driver with a character device interface called spidev. Footnote 2 has some instruction on getting it to work with Gumstix Overo boards. This article is not about spidev, but about writing a customized SPI device driver.
There are a couple of reasons to write a customized driver.
Kernel ConfigurationFor the OMAP3's, the SPI controller driver is omap2_mcspi.c. The kernel config option to include the omap2_mcspi driver is CONFIG_SPI_OMAP24XX. You can configure it as built-in or as a loadable module. If you are using the GUI menuconfig tool, you can find the module here Device Drivers
The default Gumstix Overo kernels already have the omap2_mcspi driver enabled.
Gumstix brings out SPI1 to the expansion board with two cable select lines, CS0 and CS1. CS0 is assigned to the ADS7846 touchscreen driver and CS1 to the LGPHILIPS LB035Q02 display in the Overo Linux board file, board-overo.c. If you want to use SPI bus 1 off the Overo expansion board, then you need to completely disable one or both of these kernel modules in your kernel config and rebuild your kernel. If you are using gumstix kernels >= 2.6.36, then SPIDEV is conditionally assigned to both CS0 or CS1 if either the ADS7846 or LGPHILIPS drivers are not enabled. This is convenient if you want to use SPIDEV, but if you want to load your own driver you will also have to disable SPIDEV in your kernel config. Instructions for modifying your kernel configuration and rebuilding can be found here - Gumstix kernel development If you are working in the menuconfig application, you can find the ADS7846 config option here Device Drivers
the LGPHILIPS LB035Q02 config option here Device Drivers
and the SPIDEV config option here Device Drivers
Here are some shortcuts, to directly modify the defconfig file for the ADS7846 touchscreen and the LGPHILIPS display sed -i 's:CONFIG_TOUCHSCREEN_ADS7846=m:# CONFIG_TOUCHSCREEN_ADS7846 is not set:g' defconfig And this will disable SPIDEV sed -i 's:CONFIG_SPI_SPIDEV=y:# CONFIG_SPI_SPIDEV is not set:g' defconfig
Pin MultiplexingThe OMAP3s have 4 SPI buses. Different boards may bring out different SPI bus pins. Many of the pins of an OMAP3 system can be multiplexed for different functions. So even if a particular SPI bus is brought out, you need to verify that the pins are multiplexed for SPI functionality and not for another function such as GPIO. The Gumstix Overo SPI1 pins are correctly mux'd for SPI use by default. The SPI1 pins on the expansion board are Pin 3 - SPI1_CLK Pin multiplexing is still done primarily in the bootloader, u-boot. There is a continuing effort by the Linux OMAP developers to move the pin mux'ing into the kernel. You can do your muxing in either place, but since the kernel follows the bootloader, values set in the kernel will override. Here are some instructions for modifying the pin muxing in u-boot - Gumstix u-boot development The values currently set in the Overo u-boot overo.h file to get SPI bus 1 functionality on the expansion boards are as follows MUX_VAL(CP(MCSPI1_CLK), (IEN | PTD | DIS | M0)) The macros MUX_VAL() and CP() and the constants used in overo.h are defined in the u-boot source file arch/arm/include/asm/arch-omap3/mux.h.
You'll see slightly different configurations in Beagleboard examples, but the important thing is to make sure that both the CLK and SOMI pins are mux'd as input enabled (IEN). The remaining SPI pins can be mux'd as outputs, pulled up or down or neither (DIS) doesn't matter. The Overo default that muxes the SIMO line as input enabled (IEN) also doesn't make a difference. IEN still allows output.
If you wanted to do the pin mux'ing in the kernel, you would do it in board-overo.c like this omap_mux_init_signal("mcspi1_clk", OMAP_PIN_INPUT);
The beagle board file is board-omap3beagle.c
See the footnote on spidev at the bottom of this page for a fuller example.
Driver SetupThe are two steps you need to get your driver recognized by the Linux SPI system.
The two steps can be done in any order, but when they are both completed, the SPI framework will respond with a call to the probe() callback in your driver. You will be given a handle to a struct spi_device in the callback that you'll use to interact with the SPI system. Once your probe function has been called you can start using the SPI bus.
Static registering of SPI devices is covered in the spidev notes section at the bottom of this page.
To dynamically register a SPI device, you can follow these steps
Here is what the code might look like (https://gist.github.com/716613)
It's a little messy, but it's all boilerplate stuff. You can copy and paste the code and only modify the spi_device fields specific to devices your driver is handling. These are the same fields you would have to set in a board data structure if you did it that way.
To register a spi driver you need to initialize a spi_driver structure with your driver name and some callback functions. At a minimum, you need to provide a probe() callback. Something like this static struct spi_driver spike_driver = {
And then somewhere in your driver code call spi_register_driver(). spi_register_driver(&spike_driver);
Code for Part 1The code for the project is here http://github.com/scottellis/spike You need a cross-compiler and the kernel source code available to build the module. There are some notes for using the OpenEmbedded tools here - Gumstix Kernel Development Once you have your tools and the linux source, you can fetch and compile the driver like this $ git clone git://github.com/scottellis/spike.git This will build a driver spike.ko Copy this driver to your system and load it with insmod. root@overo:~# insmod spike.ko You might get this error message if omap2_mcspi is not loaded. root@overo:~# insmod spike.ko If so, load omap2_mcspi first root@overo:~# modprobe omap2_mcspi The driver declares a spi device and registers the driver spike for this device. If you read from /dev/spike device you'll get the following root@overo:~# cat /dev/spike
The next section will start reading and writing some data Part 2 - SPI message basics
Notes1. For kernel versions < 2.6.34 you will need a patch for a bug in the omap2_mcspi cleanup code that impacts dynamically introduced slave devices. Without it you'll oops under certain module insertion conditions. You can find the patch for the oops in the spike project patches directory.
2. spidev is a generic SPI protocol driver that exposes a userland char-dev interface. You can find the official documentation for spidev here. I haven't used spidev, but I can provide some instruction for getting enabled. Spidev is not setup to handle dynamic declaration of spi devices, so you have to modify the kernel to enable it, typically in your kernel board file.
In the kernel header include/linux/spi/spi.h is a struct spi_board_info structure that should be populated for any statically registered spi device. You then add the device to the system using the spi_register_board_info() function.
Here is what it looks like (https://gist.github.com/736463)
You could register a spidev device on SPI bus 1 CS0 this way static struct spi_board_info spidev_board_info {
And then add it like this spi_register_board_info(&spidev_board_info, 1);
Or if you wanted to register both CS 0 and CS 1 for spidev use static struct spi_board_info spidev_board_info[] {
There is an OE ready patch to enable spidev in the patches directory of the spike project on github here - spidev-enable.patch. Refer to the Gumstix kernel development page, 'Patching the board file' section, for details on how to use this patch with OE. To use this patch, you still have to disable either the ADS7846 or the LGPHILIPS display driver to free up one of the expansion board SPI1 CS lines. Update - This patch is now incorporated in the Gumstix OE repository for kernel 2.6.36 so all you need to do is disable one or both of the ADS7846 touchscreen or LGPHILIPS display to use spidev. Older kernels still need the spidev-enable patch.
Of course, you could do the same static device declaration with any custom SPI driver. It would look like this for the spike driver in this article static struct spi_board_info spike_board_info {
Then you would have to remove the add_spike_device_to_bus() code in spike.c or at least the call to that function. The end result would be a spike driver that works the same way.
|
Writing a Linux SPI driver