I. Overview
Developing Drivers Based on subsystems is a common practice in Linux kernel. I have previously written about driver development based on the I2C subsystem. This article introduces another common bus SPI development method. The development of the SPI subsystem has many similarities with I2C, so you can compare and learn. This topic is divided into two parts. The first part introduces the theoretical framework of SPI-based sub-system development. The second part takes the m25p10 chip on the fs_s5pc100 far-sighted Teaching Platform of Huaqing as an example (kernel version 2.6.29 ), compile an SPI driver instance.
II. Introduction to SPI bus protocol
Before introducing driver development, you need to familiarize yourself with several key points in the SPI communication protocol. When writing drivers, you need to consider relevant factors.
The SPI bus consists of four signal lines: miso (serial data input), Mosi (serial data output), sck (Serial shift clock), and CS (enable signal. For example, the m25p10 chip on fs_s5pc100 is wired:
The D foot of m25p10 is its data input foot, Q is the data output foot, and C is the clock foot.
SPI is commonly used in four data transmission modes. The main difference is that the output serial synchronous clock polarity (cpol) and phase (cpha) can be configured. If cpol = 0, the idle status of the serial synchronization clock is low. If cpol = 1, the idle status of the serial synchronization clock is high. If cpha = 0, data at the frontend (up or down) of the serial synchronization clock is sampled; If cpha = 1, data at the back edge (up or down) of the serial synchronization clock is sampled.
Which of the four modes depends on the device. For example, the m25p10 Manual specifies that it supports the following two modes: cpol = 0 cpha = 0 and cpol = 1 cpha = 1.
Iii. SPI driver development in Linux
First, define the SPI driver level, such:
The above figure shows the idea.
1. Platform Bus
The structure of platform bus is platform_bus_type, which is defined at the beginning. We do not need to define.
2. platform_device
The SPI controller corresponds to the platform_device definition method, also take the SPI controller in s5pc100 as an example, see ARCH/ARM/plat-s5pc1xx/dev-spi.c File
Structplatform_device initi_device_spi0 = {
. Name = "s3c64xx-spi", // name, to match platform_driver
. ID = 0, // 0th controllers, three controllers in s5pc100
. Num_resources = array_size (s5pc1xx_spi0_resource), // resource usage type
. Resource = s5pc1xx_spi0_resource, // pointer to the resource structure array
. Dev = {
. Dma_mask = & spi_dmamask, // DMA addressing range
. Coherent_dma_mask = dma_bit_mask (32), // you can disable the cache and other measures to ensure consistent DMA addressing range
. Platform_data =& S5pc1xx_spi0_pdata, // For special platform data, see the following
},
};
Static struct89c64xx_spi_cntrlr_infoS5pc1xx_spi0_pdata= {
. Cfg_gpio =S5pc1xx_spi_cfg_gpio, // Used for the IO configuration of the controller pin
. Export o_lvl_mask = 0x7f,
. Rx_lvl_offset = 13,
};
Static intS5pc1xx_spi_cfg_gpio(Structplatform_device * pdev)
{
Switch (pdev-> ID ){
Case 0:
Initi_gpio_cfgpin (s5pc1xx_gpb (0), s5pc1xx_gpb0_spi_miso0 );
Initi_gpio_cfgpin (s5pc1xx_gpb (1), s5pc1xx_gpb1_spi_clk0 );
Initi_gpio_cfgpin (s5pc1xx_gpb (2), s5pc1xx_gpb2_spi_mosi0 );
Initi_gpio_setpull (s5pc1xx_gpb (0), initi_gpio_pull_up );
Initi_gpio_setpull (s5pc1xx_gpb (1), initi_gpio_pull_up );
Initi_gpio_setpull (s5pc1xx_gpb (2), initi_gpio_pull_up );
Break;
Case 1:
Initi_gpio_cfgpin (s5pc1xx_gpb (4), s5pc1xx_gpb4_spi_miso1 );
Initi_gpio_cfgpin (s5pc1xx_gpb (5), s5pc1xx_gpb5_spi_clk1 );
Initi_gpio_cfgpin (s5pc1xx_gpb (6), s5pc1xx_gpb6_spi_mosi1 );
Initi_gpio_setpull (s5pc1xx_gpb (4), initi_gpio_pull_up );
Initi_gpio_setpull (s5pc1xx_gpb (5), initi_gpio_pull_up );
Initi_gpio_setpull (s5pc1xx_gpb (6), initi_gpio_pull_up );
Break;
Case 2:
Initi_gpio_cfgpin (s5pc1xx_gpg3 (0), s5pc1xx_gpg3_0_spi_clk2 );
Initi_gpio_cfgpin (s5pc1xx_gpg3 (2), s5pc1xx_gpg3_2_spi_miso2 );
Initi_gpio_cfgpin (s5pc1xx_gpg3 (3), s5pc1xx_gpg3_3_spi_moi2 );
Initi_gpio_setpull (s5pc1xx_gpg3 (0), initi_gpio_pull_up );
Initi_gpio_setpull (s5pc1xx_gpg3 (2), initi_gpio_pull_up );
Initi_gpio_setpull (s5pc1xx_gpg3 (3), initi_gpio_pull_up );
Break;
Default:
Dev_err (& pdev-> Dev, "invalidspi controller number! ");
Return-einval;
}
3. platform_driver
Let's look at platform_driver again. See the drivers/SPI/spi_cloud64xx.c file.
Static structplatform_driver initi64xx_spi_driver = {
. Driver = {
. Name = "s3c64xx-spi", // name, corresponds to platform_device
. Owner = this_module,
},
. Remove = maid,
. Suspend = maid,
. Resume = initi64xx_spi_resume,
};
Platform_driver_probe (& courseware 64xx_spi_driver, courseware 64xx_spi_probe); // register the courseware driver
After matching with the platform_device registered in the platform and the platform, the system calls the initializ_probe method. Then, based on the input platform_device parameter, construct a structure spi_master used to describe the SPI controller and register it. Spi_register_master (master ). You need to select your own spi_master for the spi_device to be registered later, and transmit SPI data using the transport function provided by spi_master.
Similar to I2C, SPI also has an object describing the Controller called spi_master. Its main members include the serial number of the Host Controller (multiple SPI host Controllers may exist in the system), number of slices, functions used in SPI mode and clock settings, and functions used for data transmission.
Struct spi_master {
Struct device dev;
S16 bus_num; // indicates the ID of the SPI host controller. Determined by the platform code
Num_chipselect; // Number of slices supported by the Controller, that is, the number of SPI devices supported
INT (* setup) (structspi_device * SPI); // set the SPI clock and data transmission mode for the device. It is called in the spi_add_device function.
INT (* Transfer) (structspi_device * SPI,
Struct spi_message * mesg); // implements bidirectional data transmission, which may cause sleep.
Void (* cleanup) (structspi_device * SPI); // called upon logout
};
4. SPI bus
The bus type corresponding to the SPI bus is spi_bus_type, which is defined in drivers/SPI. C of the kernel.
Struct bus_typespi_bus_type = {
. Name = "SPI ",
. Dev_attrs = spi_dev_attrs,
. Match = spi_match_device,
. Uevent = spi_uevent,
. Suspend = spi_suspend,
. Resume = spi_resume,
};
The matching rules are as follows (the matching rules in later versions are slightly changed, and id_table is introduced to match multiple SPI device names ):
Static intspi_match_device (struct device * Dev, struct device_driver * DRV)
{
Const struct spi_device * SPI = to_spi_device (Dev );
Return strcmp (SPI-> modalias, DRV-> name) = 0;
}
5. spi_device
The following describes how to build and register a spi_device. Spi_device is a device attached to the SPI bus. Therefore, you should specify the characteristics, transmission requirements, and bus of the device when describing the device.
Static structspi_board_infoIniti_spi_devs[] _ Initdata = {
{
. Modalias = "m25p10 ",
. Mode = spi_mode_0, // cpol = 0, cpha = 0 select the specific data transmission mode.
. Max_speed_hz = 10000000, // maximum SPI clock frequency
/* Connected to SPI-0 as 1st slave */
. Bus_num = 0, // The device is connected to SPI controller 0
. Chip_select = 0, // The part selection line number. In the s5pc100 controller driver, it is not used as the basis for the part selection, but the method in controller_data below is selected.
. Controller_data = & smdk_spi0_csi [0],
},
};
Static struct89c64xx_spi_csinfo smdk_spi0_csi [] = {
[0] = {
. Set_level = smdk_m25p10_cs_set_level,
. Fb_delay = 0x3,
},
};
Static void smdk_m25p10_cs_set_level (inthigh) // This method is used by the SPI controller to set CS
{
U32 val;
Val = readl (s5pc1xx_gpbdat );
If (high)
Val | = (1 <3 );
Else
Val & = ~ (1 <3 );
Writel (Val, s5pc1xx_gpbdat );
}
Spi_register_board_info(Cloud_spi_devs, array_size (cloud_spi_devs); // register spi_board_info. This code registers spi_board_info to the linked list board_list.
In fact, the registration of spi_master mentioned above will be performed after spi_register_board_info. During the registration of spi_master, scan_boardinfo will be called to scan board_list, locate the SPI device attached to it, and then create and register the spi_device.
Static voidscan_boardinfo (struct spi_master * master)
{
Struct boardinfo * Bi;
Mutex_lock (& board_lock );
List_for_each_entry (Bi, & board_list, list ){
Struct spi_board_info * chip = Bi-> board_info;
Unsigned N;
For (n = Bi-> n_board_info; n> 0; n --, chip ++ ){
If (chip-> bus_num! = Master-> bus_num)
Continue;
/* Note: This relies onspi_new_device
* Issue diagnostics when given bogus inputs
*/
(Void) spi_new_device (master, Chip); // creates and registers the spi_device
}
}
Mutex_unlock (& board_lock );
}
6. spi_driver
This document takes the/driver/MTD/devices/m25p80. c driver in the Linux kernel as a reference.
Static struct spi_driverm25p80_driver = {// build spi_driver
. Driver = {
. Name = "m25p80 ",
. Bus = & spi_bus_type,
. Owner = this_module,
},
. Probe = m25p_probe,
. Remove =__ devexit_p (m25p_remove ),
*/
};
Spi_register_driver (& m25p80_driver); // registration of spidriver
M25p_probe is called when a matched SPI device exists.
Static int _ devinitm25p_probe (struct spi_device * SPI)
{
......
}
The corresponding spi_master can be found based on the passed spi_device parameter. Next we can use the SPI subsystem to complete data interaction for us. See the m25p80_read function. To complete the transmission, first understand the meanings of the following structures: (for the definitions and comments of these two structures, see include/Linux/SPI. h)
Spi_message: describes a complete transmission, that is, CS signal transmission from high to bottom to high.
Spi_transfer: Multiple spi_transfer is enough to generate one spi_message.
Example: The read process of m25p80 is as follows:
It can be divided into two SPI _ transfer: one is a write command, and the other is a read data. For specific implementation, see the m25p80_read function in m25p80. C. This function is extracted from the following content.
Structspi_transfer T [2];// Defines two spi_transfer
Structspi_message m;// Defines a spi_message
Spi_message_init(& M); // initialize its transfers linked list
T [0]. tx_buf = flash-> command;
T [0]. Len = pai_size + fast_read_dummy_byte; // defines the write pointer and length of the first transfer.
Spi_message_add_tail(& T [0], & M); // Add to spi_message
T [1]. rx_buf = Buf;
T [1]. Len = Len; // defines the read pointer and length of the second transfer.
Spi_message_add_tail(& T [1], & M); // Add to spi_message
Flash-> Command [0] = opcode_read;
Flash-> Command [1] = from> 16;
Flash-> Command [2] = from> 8;
Flash-> Command [3] = from; // initialize the Buf content
Spi_sync(Flash-> SPI, & M); // call spi_master to sendSpi_message
// The spi_sync method is synchronous transmission. You can also use the spi_async asynchronous method. In this case, you need to set the callback to complete the function.
You can also select some encapsulated functions that are easier to use. These functions can be found in the include/Linux/SPI. h file, for example:
Extern intSpi_write_then_read(Struct spi_device * SPI,
Const u8 * txbuf, unsigned n_tx,
U8 * rxbuf, unsigned n_rx );
This blog post is here. The next article provides a complete driver for m25p10.