SPI Driver for Linux Based Embedded System

Today Linux is the operating system choice for a wide range of special-purpose electronic devices known as embedded systems. An embedded system is specifically designed to perform a set of designated activities, and it generally uses custom, heterogeneous processors. This makes Linux a flexible operating system capable of running on a variety of architectures, such as ARM and many others.

Linux has highly modular architecture and it facilitates the porting and a lot of efforts are required to build new kernel components to fully support the target platform. A big part of these efforts are in developing the low-level interfaces commonly referred to as device drivers. A device driver is a piece of software designed to direct control a specific hardware resource using an hardware-independent well defined interface.

The kernel SPI subsystem is divided into Controller Driver and Protocol Drivers.

Controller Driver
A controller driver is represented by the structure spi_master. The driver for an SPI controller manages access to spi slave devices through a queue of spi_message transactions, copying data between CPU memory and an SPI slave device. For each such message it queues, it calls the message’s completion function when the transaction completes.

Protocol Driver
A Protocol driver is represented by the structure spi_driver, they pass messages through the controller driver to communicate with a Slave or Master device on the other side of an SPI link. For example one protocol driver might talk to the MTD layer to export data to file systems stored on SPI flash like Data Flash; and others might control audio interfaces, present touchscreen sensors as input interfaces, or monitor temperature and voltage levels during industrial processing. And those might all be sharing the same controller driver.

Initializing and probing of SPI Controller Driver

For embedded System-on-Chip (SOC) based boards, SPI master controllers connect to their drivers using some non SPI bus, such as the platform bus. Initializing and probing SPI controller driver is performed using the platform bus. During the initial stage of platform driver registration stage of probe() in that code includes calling spi_alloc_master which allocated the structure spi_master in the kernel and during final stage calling spi_register_master() to hook up to this SPI bus glue.

SPI controller’s will usually be platform devices, and the controller may need some platform_data in order to operate properly. The “struct platform_device” will include resources like the physical address of the controller’s first register and its IRQ.

Platforms will often abstract the “register SPI controller” operation,maybe coupling it with code to initialize pin configurations in the board initialization files. This is because most SOCs have several SPI-capable controllers, and only the ones actually usable on a given board should normally be set up and registered.

1 /* spi bus : spi0 */
2 static struct platform_device mysoc_spi_dev0 = {
3 .name = “mysoc_spi”,
4 .id = 0,
5 . resource = &mysoc_spi_resources [0] ,
6 . num_resources = 2,
7 . dev = {
8 . platform_data = &mysoc_spi_dev0_data ,
9 },
10 };
11
21 static int __init mysoc_spi_init ( void )
22 {
23 …
24 platform_device_register (&mysoc_spi_dev0 );
25 …
28 }
29

Listing 1: Registration of the SPI platform device with the platform bus.

On the driver’s side, the registration with the platform bus is achieved by populating a structure platform_driver and passing it to the macro module_platform_driver() as argument (Listing-2). The platform bus simply compares the driver.name member against the name of each device, as defined in the platform_device data structure (Listing 1); if they are the same the device matches the platform driver.

1 # define DRIVER_NAME “mysoc_spi”
2
3 static struct platform_driver mysoc_spi_driver = {
4 .probe = mysoc_spi_probe ,
5 .remove = mysoc_spi_remove,
6 .driver = {
7 .name = DRIVER_NAME ,
8 .owner = THIS_MODULE ,
9 .pm = &mysoc_spi_pm_ops ,
10 },
11 };
12 module_platform_driver ( mysoc_spi_driver );

Listing 2: Registration of the SPI platform driver with the platform bus.

As usual, binding a device to a driver involves calling the driver’s probe() function passing a pointer to the device as a parameter. The SPI controller driver registers with the SPI subsystem by using a structure spi_master that is instantiated and initialized by the SPI platform driver’s probe() function, as shown in Listing 3.

The sequence of operations performed on probing are the following:

  1. Get the device resource definitions.
  2. Allocate the appropriate memory and remap it to a virtual address for being accessed by the kernel.
  3. Load the device settings.
  4. Configure the device hardware.
  5. Register with the power management system.
  6. Create the per-device sysfs nodes.
  7. Request the interrupt and register the IRQ.
  8. Set up the struct spi_master and register the master controller driver with the SPI core.

On successful completion of above steps the driver is bounded to the devices representing the mysoc SPI controllers.

1 /* Probe function */
2 static int mysoc_spi_probe ( struct platform_device * pdev )
3 {
4 …
5 struct spi_master *master;
6 …
7 /* Allocate master */
8 master = spi_alloc_master(&pdev->dev, sizeof(struct spi_master));
9 …
10 /* Register with the SPI framework */
11 status = spi_register_master(master);
12 …
14 }

Listing 3: Registration of the SPI Controller Driver.

Then it will scan the platform data to find the SPI devices connected to this SPI bus. The function scan_boardinfo() scans the platform data, and call spi_new_device() to create SPI device data structure, and set up struct spi_device based on the platform information. Then it calls the master’s setup() method to further initialize, link the struct spi_device with struct spi_master, and add the SPI device to the system. To this point, the SPI master and SPI device are created and added to the system. But it still can’t communicate with the SPI device as no specific driver is installed yet.

Initializing and probing of SPI Protocol Driver

SPI Protocol driver’s deal with the spi chip attached to the SPI controller. These drivers are responsible to send/receive to/from the device. These device drivers expose user-level API (like spidev does) or kernel-level API that can be used by another subsystem. For example, the touch controller chip ADS7846, which is connected to the SPI bus, provides a touch interface and connects to input subsystem to generate input events.

SPI board information is part of the machine-depended code that performs registration of SPI devices with the SPI subsystem. Because SPI devices are usually hardwired to the board and rarely have an ability to enumerate them, they have to be hardcoded in machine board file in the Linux kernel. The board-dependent code does the registration by calling the function spi_register_board_info. It takes two parameters: list of devices connected and the size of this list (Listing-4).

1 /* SPI Device */
2 static struct ads7846_platform_data ads_info = {
3 .vref_delay_usecs = 100,
4 .x_plate_ohms = 580,
5 .y_plate_ohms = 410,
6 };
7 static struct spi_board_info mysoc_spi_devices [] __initdata = {
8 {
9 .modalias = “ads7846”,
10 .platform_data = &ads_info,
11 .mode = SPI_MODE_0,
12 .irq = GPIO_IRQ(31),
13 .max_speed_hz = 120000,
14 .bus_num = 1,
15 .chip_select = 0,
16 },
17 };
18 static void __init mysoc_platform_init ( void )
19 {
20 …
21 /* Register SPI devices on bus #0 */
22 spi_register_board_info (mysoc_spi_devices, ARRAY_SIZE ( mysoc_spi_devices ));
23 …
24 }

Listing 4: Registration of the SPI devices.

During initialization the driver registers itself with the SPI core. This is achieved by populating a structure spi_driver and passing it as argument to the function spi_register_driver(), as shown in Listing-5.

1 static struct spi_driver ads7846_driver = {
2 . driver = {
3 . name = “ads7846”,
4 .owner=THIS_MODULE,
5 },
6 . probe = ads7846_probe ,
7 . remove = ads7846_remove ),
8 };
9
10 /* Module init */
11 static int __init ads7846_init ( void )
12 {
13 return spi_register_driver (& ads7846_driver );
14 }

Listing-5: Registration of the ADS7846 driver.

The structure spi_driver holds pointers to the probe and remove functions that are executed respectively on device probing and when the device is removed. The names of the supported devices are important for binding.

During boot the kernel looks for any SPI driver that has registered a matching device name, that is “ads7846”. Upon finding such a driver, the kernel invokes its probe() function passing a pointer to the ADS7846 device as a parameter. This process is called probing. The probe function is responsible for the per-device initialization, that is initializing hardware, allocating resources, and registering the device with any appropriate subsystem.

More in detail, the ADS7846 probe function takes the following actions:

  1. Allocate memory for spi_transfer and spi_message data structure.
  2. Load the device settings.
  3. Configure the device hardware.
  4. Create the per-device sysfs nodes.
  5. If the device interrupt feature is enabled, request the interrupt and register the IRQ.
  6. If the device polling feature is enabled, register the device with the input subsystem.

On successful completion of all the above steps, meaning a successful probing, the device is bound to the driver.

This entry was posted in Technical and tagged , , , . Bookmark the permalink.

4 Responses to SPI Driver for Linux Based Embedded System

  1. trupti says:

    I am writing SPI platform driver,for connecting lpc1768 to my linux system(raspberrypi3).
    i am getting one error in spi_register_board_info. macro.
    my code is:

    static struct spi_board_info lpc1768_board_info[] __initdata = {
    {
    .modalias = “lpc1768”,
    .chip_select = 0,
    },
    };
    spi_register_board_info(lpc1768_board_info,1);

    and error is:
    /home/trupti/SVS/pro_driver/lpc_driver/spi-lpc1768.c:65:44: error: expected ‘)’ before numeric constant
    spi_register_board_info(lpc1768_board_info,1);

    i googled it.but i am not getting any help.
    please help me to remove error.
    thank you in advance..

  2. invo-tronics says:

    Can you use – spi_register_board_info(lpc1768_board_info,ARRAY_SIZE(lpc1768_board_info));
    instead of passing a numeric constant

  3. ravi says:

    Hi ,
    Am new on SPI driver on Linux, could you please share information , how to write spi driver from scratch .
    please share any link , it’s more helpful for me.
    Regards,
    Ravi.M

  4. ami says:

    Hello,

    Thanks for the nice explanation.
    Please locate the complete code related to this example, it would be more helpful.

    Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *