Timeline
Timeline
2025-12-30
init
This article introduces the basics of the SPI bus protocol in Linux drivers. It first explains the origin and design philosophy of SPI (Serial Peripheral Interface), noting that it was proposed by Motorola to achieve full-duplex communication with a minimum number of pins, and compares the differences between simplex, half-duplex, and full-duplex communication. It then details SPI's synchronous communication mechanism, master-slave structure design, and the normal mode and daisy-chain mode that support multiple slave devices. In the hardware connection section, it introduces the functions of the four signal lines SCLK, MOSI, MISO, and CS one by one, as well as the connection relationships between master and slave devices. Finally, the article summarizes the SPI communication principles, including the default transmission of the most significant bit (MSB) first, data line level definitions, and the mechanism by which the master selects a slave by pulling the CS line low and simultaneously completes read and write operations through MOSI and MISO under the drive of clock pulses.
Linux Driver Notes
| Table of Contents | Links |
|---|---|
| 1. Linux Driver Framework | |
| 2. Linux Driver Loading Logic | |
| 3. Character Device Basics | |
| 4. Concurrency and Race Conditions | |
| 5. Advanced Character Device Topics | |
| 6. Interrupts | |
| 7. Platform Bus | |
| 8. Device Tree | |
| 9. Device Model | |
| 10. Hotplug | |
| 11. pinctrl Subsystem | |
| 12. GPIO subsystem | |
| 13. Input subsystem | |
| 14. 1-Wire | |
| 15. I2C | |
| 16. SPI | |
| 17. UART | |
| 18. PWM | |
| 19. RTC | |
| 20. Watchdog | |
| 21. CAN | |
| 22. Network devices | |
| 23. ADC | |
| 24. IIO | |
| 25. USB | |
| 26. LCD |
SPI Basics
SPI Features
SPI (Serial Peripheral Interface) is a serial communication protocol originally proposed and developed by Motorola in the late 1980s. At that time, with the development of microcontroller technology, more and more peripheral devices needed to exchange data with microcontrollers.
However, traditional parallel bus interfaces had problems such as a large number of pins, complicated wiring, and high power consumption, making them unsuitable for embedded systems.
To this end, Motorola designed SPI, a simple and efficient serial communication bus protocol, to solve the pain points of communication interfaces in embedded systems at that time. The core design ideas of SPI are as follows:
Use the minimum number of pins to achieve full-duplex communication
Simplex communication: It means the signal is transmitted in only one direction, and can only send or receive.
Half-duplex communication: signals can be transmitted in both directions, but at any given moment only sending or receiving is allowed (single bus and I2C are both half-duplex communication).
Full-duplex communication: Full-duplex communication means data is transmitted in both directions simultaneously. SPI only needs 4 signal lines (SCLK、MOSI、MISO、CS) to complete data exchange between master and slave devices, achieving full-duplex communication. This greatly reduces the number of pins and simplifies wiring.
Adopt a synchronous communication mechanism
Synchronous communication: means that the sender and receiver of data transmission use the same clock signal for coordination. Eachclock cycle, one data bit is sent or received., therefore, data transmission strictly depends on the rhythm of the clock signal.
Asynchronous communication: does not rely on a common clock signal for data transmission, the sender and receiver operate independently and synchronize data through a specific protocol. The specific schematic diagram is as follows:

Master-slave structure design: The SPI protocol divides communication devices into master devices and slave devices
- The master device is responsible forproviding the clock signal and controlling the communication process.
- Slave deviceIt passively responds to the master device’s operations.
Supports multiple slave devices.: SPI has multiple connection modes, mainly divided into normal mode and daisy-chain mode (daisy-chain mode is not commonly used),
BeforeNormal modeIn this mode, each slave device has an independent Chip Select (CS) signal line. The master device selects a slave device for communication by pulling the corresponding CS line low, as shown in the diagram below:

Normal mode Daisy-chain modeIn this mode,
slave devices are connected in series Data is transmitted from the master device to the first slave device, then from the first slave device to the next, thus forming a chain structure. The specific connection diagram is shown below:

Daisy-chain mode
SPI hardware connection
SPI uses 4 signal lines for communication, namely: SCLK、MOSI、MISO and CSNow, the specific role and function of each signal line will be introduced.
- SCLK (Serial Clock): Clock signal line
This is a synchronous clock signal generated by the master device, used to drive data transmission and reception.
- MOSI (Master Output Slave Input): Master-to-Slave data transmission line
This is the line through which the master device transmits data to the slave device. The master device places the data to be sent on this line, and the slave device reads data from this line.
- MISO (Master Input Slave Output): Slave-to-Master data transmission line
This is the line for transmitting data from the slave device to the master device. The slave device places the data to be sent on this line, and the master device reads data from this line.
- CS (Chip Select) or SS (Slave Select): chip select/slave device select signal
This is the signal line used by the master device to select the slave device with which it communicates. When the master device pulls the CS/SS line of a slave device low, it indicates that the slave device is selected for communication. The master device can select different slave devices to communicate with by controlling multiple CS/SS lines.
The connection relationship between the master device and the slave device is as follows:
The SCLK line connects the master device’s SCLK output to the slave device’s SCLK input
The MOSI line connects the master device’s MOSI output to the slave device’s MOSI input
The MISO line connects the master device’s MISO input to the slave device’s MISO output
The CS/SS line connects the master device’s chip select output to the slave device’s chip select input

SPI communication principle
When the SPI bus performs data transmission, it has the following characteristics:
- Transmission order: By default, the SPI bus transmits the most significant bit (MSB) first, followed by the least significant bit (LSB).
- Logic level: A high level on the data line represents logic 1, and a low level on the data line represents logic 0.
- Byte transmission:
After one byte transmission is completed,The next byte transmission can be started without an acknowledgment signal.。
SPI is a single-master communication protocol, meaning that only one master device in the bus can initiate communication, and both the SPI master device and the slave device have aserial shift register, as shown in the figure below:

When the SPI master device wants to read/write the slave device, itfirst pulls the corresponding CS line of the slave device low(the CS line is active low), then starts sending working pulses on the clock line, and at the corresponding pulse times,the master device sends signals to MOSI to implement ‘write’, and at the same time can sample MISO to implement ‘read’。
Within one SPI clock cycle, transmission and reception occur simultaneously.:
- The host sends 1 bit of data via the MOSI line, and the slave reads this 1 bit of data via that line;
- The slave sends 1 bit of data via the MISO line, and the master reads this 1 bit of data through that line;
When all the contents in the registers have been shifted out, it is equivalent to completing the exchange of the contents of the two registers. If the master device wants to transmit data to the slave device, the master device only needs to ignore the data received from the slave device. If the master device wants to receive data from the slave device, the master device needs to send arbitrary data (such as 0xFF) to the slave device to generate the clock, and the slave device ignores the data received from the master device.
SPI Polarity and Phase
Before SPI communication, it is necessary to first Determine the default state of the clock signal. and sampling time of the clock signal, these two parameters are determined by CPOL (Clock Polarity) and CPHA (Clock Phase) to determine
- CPOL (Clock Polarity): CPOL defines
Default state of the clock signal(i.e., idle state) 。- When CPOL = 0, it indicates that the clock signal is at low level (0) in the idle state.
- When CPOL = 1, it indicates that the clock signal is at high level (1) in the idle state.
- CPHA (Clock Phase): CPHA defines
Sampling time of the data signal relative to the clock signal 。- When CPHA = 0, it indicates that the data is at the clock’sThe first edge (rising or falling) is sampled.。
- When CPHA = 1, it indicates that the data is on the clock’sThe second edge (rising or falling) is sampled.。
Four combination modes:
- CPOL=0, CPHA=0: Clock idle is low, data is sampled on the first edge (rising edge) of the clock

- CPOL=0, CPHA=1: Clock idle is low, data is sampled on the second edge (falling edge) of the clock

- CPOL=1, CPHA=0: Clock idle is high, data is sampled on the first edge (falling edge) of the clock

- CPOL=1, CPHA=1: Clock idle is high, data is sampled on the second edge (rising edge) of the clock

Note:The master and slave devices must be configured with the same CPOL and CPHA, otherwise communication will not work properly.
In general, the master and slave devices must be configured with the same CPOL and CPHA to communicate properly.
SPI Interface in the iTOP-RK3568 Processor
The iTOP-RK3568 supports 4 SPI controllers
- 1 controller supports 1 chip select output, while the remaining 3 controllers each support 2 chip select outputs.
- Supports master mode and slave mode, switchable via software configuration.

The 4 SPI interfaces here refer to hardware SPI. The SoC has dedicated hardware SPI circuits. The introduction to hardware SPI is as follows:
Hardware SPI:
- Implementation: The SPI communication protocol is implemented through dedicated hardware circuits.
- Advantages:
- Low CPU usage: The SPI bus is automatically handled by the hardware circuit, and the CPU does not need to intervene directly.
- High transmission rate: It can achieve communication rates of tens of MHz or even higher.
- Flexible configuration: Hardware SPI usually provides CPOL and CPHA configuration options to set clock polarity and phase.
- Disadvantages:
- Requires dedicated hardware: It requires support from a dedicated hardware SPI interface module, and the cost is relatively high.
- Fixed interface: The interface is fixed and not as flexible as software SPI.
- Applicable scope: Suitable for high-speed, large-volume data transmission scenarios, such as connecting peripherals like LCD, EEPROM, etc.
Software SPI refers to simulating the four signal lines of SPI through GPIO pins. When hardware SPI is insufficient, GPIO can be used to emulate software SPI. The introduction to software SPI is as follows:
Software SPI:
- Implementation method: Simulate the SPI communication protocol through the CPU’s GPIO pins.
- Advantages
- High flexibility: CPOL and CPHA can be flexibly configured, suitable for highly customized SPI communication.
- No dedicated hardware requirement: No dedicated hardware SPI interface module is needed, suitable for systems without a built-in hardware SPI interface.
- Disadvantages:
- High CPU usage: The CPU program is required to complete all operations such as clock signal generation and data read/write.
- Low transmission rate: The speed is limited by the CPU’s program execution speed, and is usually slower than hardware SPI.
- Applicable scope: Suitable for systems without a built-in hardware SPI interface, or scenarios requiring highly customized SPI communication.
The specific usage of the SPI interface on the RK3568 development board is shown in the following table.
SPI0
| pinctrl function | Net label | Corresponding GPIO | Function |
|---|---|---|---|
| SPI0_CLK_M0 | TP_INT_L_GPI00_B5 | GPIO00_B5 | PCIE2.0 wake-up |
| SPI0_MISO_M0 | LCD1_PWREN_H_GPI00_C5 | GPIO00_C5 | Unused |
| SPI0_MOSI_M0 | TP_RST_L_GPI00_B6 | GPIO00_B6 | MIPI screen touch reset pin |
| SPI0_CS0_M0 | 4G_PWREN_H_GPI00_C6 | GPIO00_C6 | Unused |
| SPI0_CS1_M0 | LCD1_BL_PWM5 | GPIO00_C4 | MIPI screen enable pin |
| SPI0_CLK_M1 | PCIE30X1_WAKEn_M1 | GPIO2_D3 | Baseboard backside SPI |
| SPI0_MISO_M1 | PCIE20_CLKREQn_M1 | GPIO2_D0 | Baseboard backside SPI |
| SPI0_MOSI_M1 | PCIE20_WAKEn_M1 | GPIO2_D1 | Baseboard backside SPI |
| SPI0_CS0_M1 | PCIE30X1_CLKREQn_M1 | GPIO2_D2 | Baseboard backside SPI |
SPI1
| pinctrl function | Net label | Corresponding GPIO | Function |
|---|---|---|---|
| SPI1_CLK_M0 | GMAC0_TXEN | GPIO2_B5 | Ethernet port 0 |
| SPI1_MISO_M0 | GMAC0_RXD0 | GPIO2_B6 | Ethernet port 0 |
| SPI1_MOSI_M0 | GMAC0_RXD1 | GPIO2_B7 | Ethernet port 0 |
| SPI1_CS0_M0 | GMAC0_RXDV_CRS | GPIO2_C0 | Ethernet port 0 |
| SPI1_CS1_M0 | CLK32K_OUT1_WIFI | GPIO2_C6 | Unused |
| SPI1_CLK_M1 | GMAC0_TXEN | GPIO3_C3 | Ethernet port 0 |
| SPI1_MISO_M1 | SPK_CTL_H_GPI03_C3 | GPIO2_B6 | 5G Reset |
| SPI1_MOSI_M1 | PCIE20_PERSTn_M1 | GPIO3_C1 | Unused |
| SPI1_CS0_M1 | PCIE30X1_PERSTn_M1 | GPIO3_A1 | Unused |
SPI2
| pinctrl function | Net label | Corresponding GPIO | Function |
|---|---|---|---|
| SPI2_CLK_M0 | ETH0_REFCLK0_25M | GPIO2_C1 | Ethernet port 0 clock |
| SPI2_MISO_M0 | GMAC0_MCLKINOUT | GPIO2_C2 | Ethernet port 0 clock |
| SPI2_MOSI_M0 | GMAC0_MDC | GPIO2_C3 | Ethernet port 0 |
| SPI2_CS0_M0 | GMAC0_MDIO | GPIO2_C4 | Ethernet port 0 |
| SPI2_CS1_M0 | GPIO2_C5 | GPIO2_C5 | PCIE power enable pin |
| SPI2_CLK_M1 | PCIE30X1_PRSNT_L_GPI03_A0 | GPIO3_A0 | PCIE2.0 |
| SPI2_MISO_M1 | PCIE30X2_PRSNT_L_GPI02_D7 | GPIO2_D7 | PCIE3.0 |
| SPI2_MOSI_M1 | PCIE30X2_PERSTn_M1 | GPIO2_D6 | PCIE3.0 |
| SPI2_CS0_M1 | PCIE30X2_WAKEn_M1 | GPIO2_D5 | PCIE3.0 |
| SPI2_CS1_M1 | PCIE30X2_CLKREQn_M1 | GPIO2_D4 | PCIE3.0 |
SPI3
| pinctrl function | Net label | Corresponding GPIO | Function |
|---|---|---|---|
| SPI3_CLK_M0 | ETH1_REFCLK0_25M_M1 | GPIO4_B3 | Ethernet port 1 clock |
| SPI3_MISO_M0 | GMAC1_RXD1_M1 | GPIO4_B0 | Ethernet port 1 |
| SPI3_MOSI_M0 | GPIO4_B2 | GPIO4_B2 | Unused |
| SPI3_CS0_M0 | GMAC1_TXEN_M1 | GPIO4_A6 | Ethernet port 1 |
| SPI3_CS1_M0 | GMAC1_RXD0_M1 | GPIO4_A7 | Ethernet port 1 |
| SPI3_CLK_M1 | 4G_DISABLE_GPI04_C2 | GPIO4_C2 | CAN1_RX |
| SPI3_MISO_M1 | GPIO4_C5 | GPIO4_C5 | Unused |
| SPI3_MOSI_M1 | HDMI_RX_INT_L_GPI04_C3 | GPIO4_C3 | CAN1_TX |
| SPI3_CS0_M1 | GPIO4_C6 | GPIO4_C6 | Unused |
| SPI3_CS1_M1 | HDMI_TX_CEC_M0 | GPIO4_D1 | HDMI CEC pin |
Each SPI controller has two groups of pinctrl, but a hardware SPI can only be multiplexed by one group of pinctrl pins at the same time.。
mcp2515
This module can not only implement SPI-to-CAN conversion, but also TTL-to-485 conversion. The related schematic diagram is as follows:

The above schematic diagram involves two chips in total, namely MCP2515 SPI-to-CAN chipand MCP2551 CAN transceiver chipHere, we mainly introduce the MCP2515 chip.
The MCP2515 is a standalone CAN protocol controller that fully supports the CAN V2.0B technical specification. It connects to the controller via a standard SPI interface. The main features are as follows:
- Full support for CAN V2.0B: supports transmission and reception of standard and extended data frames and remote frames.
- Efficient filtering: built-in two acceptance mask registers and six acceptance filter registers, which can filter out unwanted messages and reduce the processing burden on the main MCU.
- SPI interface: communicates with the main controller via the SPI interface, providing efficient data transfer.
- Multiple operating modes: including normal mode, sleep mode, listen mode, and loopback mode to meet different application requirements.
- Automatic retransmission: automatically retransmits on transmission failure to ensure reliable data transfer.
- Error detection and handling: built-in error detection and handling mechanisms ensure the accuracy of data transmission.
The specific connection diagram between the SPI-to-CAN module and the iTOP-RK3568 development board is shown below.

SPI Subsystem Framework

The SPI subsystem above can be divided into three layers: user space, kernel space, and hardware layer. Kernel space includes the SPI device driver layer, SPI core layer, and SPI adapter driver layer. The main content of this chapter is to introduce the kernel space in the SPI subsystem framework.
SPI Device Driver Layer
The main function of the SPI device driver layer is to write drivers so that SPI peripherals can work properly. It creates corresponding device nodes and provides standardized interfaces, allowing upper-layer applications to interact with SPI devices conveniently.
Specifically, the SPI device driver layer includes the following key parts:
spi_device- Represents a slave device connected to the SPI bus.
- Contains information such as the slave device’s address and the SPI master device it belongs to.
/dev/spiXDevice Node- Provides the device access interface for upper-layer applications.
- By opening/reading/writing/controlling the device node, applications can interact with the SPI device.
- The kernel SPI subsystem is responsible for forwarding application operations to the corresponding spi_driver.
spi_driver- Implements the driver for a specific SPI slave device.
- Responsible for device initialization, read/write, configuration, and other operations.
- Interacts with the device through spi_device.
- Provides standardized interfaces for device access to the upper layer.
SPI Adapter Driver Layer
The SPI adapter driver layer is an important part of the SPI subsystem and is responsible for implementing drivers for specific SPI hardware controllers. The functions of the SPI adapter driver are as follows:
- Provide a standardized SPI transmission interface
- The adapter driver layer provides a standardized transmission interface for the SPI core layer, ensuring that different SPI controllers can uniformly use these interfaces for data transmission.
- Implement timing control and data transmission/reception of the SPI bus protocol
- Responsible for implementing the timing control of the SPI bus protocol, including the configuration of clock polarity (CPOL) and clock phase (CPHA).
- Manage the sending and receiving of data, ensuring the accuracy and reliability of data transmission.
- Automatically complete clock signal generation and data transmission/reception through the hardware SPI module, improving communication efficiency.
- Manage slave devices on the SPI bus
- The adapter driver layer is responsible for managing all slave devices on the SPI bus, including registering and unregistering slave devices.
- Ensure that slave devices on the SPI bus can communicate correctly, coordinating the interaction between the master device and slave devices.
- Handle SPI bus errors and abnormal conditions
- The adapter driver layer is responsible for monitoring and handling errors and abnormal conditions on the SPI bus.
- Provide error recovery and retry mechanisms to ensure system stability and reliability.
- Handle hardware interrupts and respond promptly to various abnormal situations during data transmission.
SPI core layer
The SPI core layer is located between the SPI device driver layer and the SPI adapter driver layer, serving as a bridge that connects the upper and lower layers, and is responsible for data transfer between the SPI device driver layer and the SPI adapter driver layer.
The main functions of the SPI core layer arespi_writeandspi_read, these functions provide basic read/write interfaces. Introduction to core functions:
spi_write- Function purpose: Used to send data to an SPI slave device.
- Function parameter introduction:
struct spi_device *spi: Pointer to the target SPI slave device.const void *buf: Data buffer.size_t len: Number of bytes to send.
- This function is responsible for generating timing and data frames that conform to the SPI protocol, and performs actual bus operations through the corresponding SPI adapter driver.
spi_read- Function purpose: Used to receive data from an SPI slave device.
- Function parameter introduction:
struct spi_device *spi: Pointer to the target SPI slave device.void *buf: Data buffer.size_t len: Number of bytes to receive.
- This function is also responsible for generating timing and data frames that conform to the SPI protocol, and performs actual bus operations through the corresponding SPI adapter driver.
The specific functions of the core layer are as follows:
- The core layer is responsible for transferring data between the device driver layer and the adapter driver layer. Through
spi_writeandspi_readfunction, the core layer passes data from the device driver layer to the adapter driver layer for actual hardware operations. - The core layer generates timing and data frames that conform to the SPI protocol, ensuring that data can be correctly transmitted on the SPI bus.
- The core layer provides standardized interfaces, allowing upper-layer device drivers to conveniently perform data transmission without needing to care about the specific implementation of the underlying hardware.
Writing the Generic SPI Peripheral Framework
Device Tree
Pins of the iTOP-RK3568 to which the SPI-to-CAN module should be connected

Based on the network labels of the pins, it can be determined that the SPI controller to be enabled is SPI0, and then the device tree of the iTOP-RK3568 can be modified.
123456789101112 | &spi0 { status = "okay"; pinctrl-0 = <&spi0m1_cs0 &spi0m1_pins>; pinctrl-1 = <&spi0m1_cs0 &spi0m1_pins_hs>; mcp2515:mcp2515@0 { compatible = "my-mcp2515"; reg = <0>; spi-max-frequency = <10000000>; status = "okay"; };}; |
Lines 3-4 specify thespi pinctrlpins, by default the spi0 controller pinctrl pins used arespi0m0_cs0andspi0m0_pins, but the second group of pinctrl pins is actually used.
Line 7 specifies chip select 0.
Line 9, setspi clkthe output clock frequency, here set to 10M, RK3568 maximum setting does not exceed 50M. IfregIf the property is not set, the device tree parsing will fail, and the driver cannot enterprobefunction;spi-max-frequencyWhen not set,max_speed_hzthe default value will be used.
regandspi-max-frequencyproperty
in rk3568.dtsi:
12345678910111213141516 | spi0: spi@fe610000 { compatible = "rockchip,rk3066-spi"; reg = <0x0 0xfe610000 0x0 0x1000>; interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>; clocks = <&cru CLK_SPI0>, <&cru PCLK_SPI0>; clock-names = "spiclk", "apb_pclk"; dmas = <&dmac0 20>, <&dmac0 21>; dma-names = "tx", "rx"; pinctrl-names = "default", "high_speed"; pinctrl-0 = <&spi0m0_cs0 &spi0m0_cs1 &spi0m0_pins>; pinctrl-1 = <&spi0m0_cs0 &spi0m0_cs1 &spi0m0_pins_hs>; num-cs = <2>; status = "disabled";}; |
According to the device tree’scompatibleproperties to find the corresponding SPI controller driver, and the specific driver file path found isspi/spi-rockchip.cThe probe function of this driver is as follows:
rockchip_spi_probe()
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 | static int rockchip_spi_probe(struct platform_device *pdev){ int ret; struct rockchip_spi *rs; struct spi_controller *ctlr; struct resource *mem; struct device_node *np = pdev->dev.of_node; u32 rsd_nsecs, num_cs, csm; bool slave_mode; struct pinctrl *pinctrl = NULL; const struct rockchip_spi_quirks *quirks_cfg; // Check whether the device node is configured as SPI slave mode slave_mode = of_property_read_bool(np, "spi-slave"); // Allocate the SPI controller based on slave or master mode if (slave_mode) ctlr = spi_alloc_slave(&pdev->dev, sizeof(struct rockchip_spi)); else ctlr = spi_alloc_master(&pdev->dev, sizeof(struct rockchip_spi)); if (!ctlr) return -ENOMEM; // Set the platform device's driver data platform_set_drvdata(pdev, ctlr); rs = spi_controller_get_devdata(ctlr); ctlr->slave = slave_mode; /* Get basic io resource and map it */ // Get basic I/O resources and map them mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); rs->regs = devm_ioremap_resource(&pdev->dev, mem); if (IS_ERR(rs->regs)) { ret = PTR_ERR(rs->regs); goto err_put_ctlr; } rs->base_addr_phy = mem->start; if (!has_acpi_companion(&pdev->dev)) rs->apb_pclk = devm_clk_get(&pdev->dev, "apb_pclk"); if (IS_ERR(rs->apb_pclk)) { dev_err(&pdev->dev, "Failed to get apb_pclk\n"); ret = PTR_ERR(rs->apb_pclk); goto err_put_ctlr; } if (!has_acpi_companion(&pdev->dev)) rs->spiclk = devm_clk_get(&pdev->dev, "spiclk"); if (IS_ERR(rs->spiclk)) { dev_err(&pdev->dev, "Failed to get spi_pclk\n"); ret = PTR_ERR(rs->spiclk); goto err_put_ctlr; } rs->sclk_in = devm_clk_get_optional(&pdev->dev, "sclk_in"); if (IS_ERR(rs->sclk_in)) { dev_err(&pdev->dev, "Failed to get sclk_in\n"); ret = PTR_ERR(rs->sclk_in); goto err_put_ctlr; } // Enable APB PCLK ret = clk_prepare_enable(rs->apb_pclk); if (ret < 0) { dev_err(&pdev->dev, "Failed to enable apb_pclk\n"); goto err_put_ctlr; } // Enable SPI CLK ret = clk_prepare_enable(rs->spiclk); if (ret < 0) { dev_err(&pdev->dev, "Failed to enable spi_clk\n"); goto err_disable_apbclk; } ret = clk_prepare_enable(rs->sclk_in); if (ret < 0) { dev_err(&pdev->dev, "Failed to enable sclk_in\n"); goto err_disable_spiclk; } // Disable SPI chip spi_enable_chip(rs, false); // Get platform interrupt resources ret = platform_get_irq(pdev, 0); if (ret < 0) goto err_disable_sclk_in; // Request interrupt ret = devm_request_threaded_irq(&pdev->dev, ret, rockchip_spi_isr, NULL, IRQF_ONESHOT, dev_name(&pdev->dev), ctlr); if (ret) goto err_disable_sclk_in; rs->dev = &pdev->dev; rs->freq = clk_get_rate(rs->spiclk); if (!rs->freq) { ret = device_property_read_u32(&pdev->dev, "clock-frequency", &rs->freq); if (ret) { dev_warn(rs->dev, "Failed to get clock or clock-frequency property\n"); goto err_disable_sclk_in; } } // Read receive sample delay (in nanoseconds) if (!device_property_read_u32(&pdev->dev, "rx-sample-delay-ns", &rsd_nsecs)) { /* rx sample delay is expressed in parent clock cycles (max 3) */ u32 rsd = DIV_ROUND_CLOSEST(rsd_nsecs * (rs->freq >> 8), 1000000000 >> 8); if (!rsd) { dev_warn(rs->dev, "%u Hz are too slow to express %u ns delay\n", rs->freq, rsd_nsecs); } else if (rsd > CR0_RSD_MAX) { rsd = CR0_RSD_MAX; dev_warn(rs->dev, "%u Hz are too fast to express %u ns delay, clamping at %u ns\n", rs->freq, rsd_nsecs, CR0_RSD_MAX * 1000000000U / rs->freq); } rs->rsd = rsd; } if (!device_property_read_u32(&pdev->dev, "csm", &csm)) { if (csm > CR0_CSM_ONE) { dev_warn(rs->dev, "The csm value %u exceeds the limit, clamping at %u\n", csm, CR0_CSM_ONE); csm = CR0_CSM_ONE; } rs->csm = csm; } rs->version = readl_relaxed(rs->regs + ROCKCHIP_SPI_VERSION); rs->fifo_len = get_fifo_len(rs);// Get FIFO length if (!rs->fifo_len) { dev_err(&pdev->dev, "Failed to get fifo length\n"); ret = -EINVAL; goto err_disable_sclk_in; } quirks_cfg = device_get_match_data(&pdev->dev); if (quirks_cfg) rs->max_baud_div_in_cpha = quirks_cfg->max_baud_div_in_cpha; // Set up and enable runtime power management pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); ctlr->auto_runtime_pm = true; ctlr->bus_num = pdev->id; ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP | SPI_LSB_FIRST; if (slave_mode) { ctlr->mode_bits |= SPI_NO_CS; ctlr->slave_abort = rockchip_spi_slave_abort; } else { ctlr->flags = SPI_MASTER_GPIO_SS; ctlr->max_native_cs = ROCKCHIP_SPI_MAX_CS_NUM; /* * rk spi0 has two native cs, spi1..5 one cs only * if num-cs is missing in the dts, default to 1 */ if (device_property_read_u32(&pdev->dev, "num-cs", &num_cs)) num_cs = 1; ctlr->num_chipselect = num_cs; ctlr->use_gpio_descriptors = true; } ctlr->dev.of_node = pdev->dev.of_node; ctlr->bits_per_word_mask = SPI_BPW_MASK(16) | SPI_BPW_MASK(8) | SPI_BPW_MASK(4); ctlr->min_speed_hz = rs->freq / BAUDR_SCKDV_MAX; ctlr->max_speed_hz = min(rs->freq / BAUDR_SCKDV_MIN, MAX_SCLK_OUT); ctlr->setup = rockchip_spi_setup; ctlr->set_cs = rockchip_spi_set_cs; ctlr->transfer_one = rockchip_spi_transfer_one; ctlr->max_transfer_size = rockchip_spi_max_transfer_size; ctlr->handle_err = rockchip_spi_handle_err; // Request TX DMA channel ctlr->dma_tx = dma_request_chan(rs->dev, "tx"); if (IS_ERR(ctlr->dma_tx)) { /* Check tx to see if we need defer probing driver */ if (PTR_ERR(ctlr->dma_tx) == -EPROBE_DEFER) { ret = -EPROBE_DEFER; goto err_disable_pm_runtime; } dev_warn(rs->dev, "Failed to request TX DMA channel\n"); ctlr->dma_tx = NULL; } // Request RX DMA channel ctlr->dma_rx = dma_request_chan(rs->dev, "rx"); if (IS_ERR(ctlr->dma_rx)) { if (PTR_ERR(ctlr->dma_rx) == -EPROBE_DEFER) { ret = -EPROBE_DEFER; goto err_free_dma_tx; } dev_warn(rs->dev, "Failed to request RX DMA channel\n"); ctlr->dma_rx = NULL; } // If both TX and RX DMA channels are successfully requested if (ctlr->dma_tx && ctlr->dma_rx) { rs->dma_addr_tx = mem->start + ROCKCHIP_SPI_TXDR; rs->dma_addr_rx = mem->start + ROCKCHIP_SPI_RXDR; ctlr->can_dma = rockchip_spi_can_dma; } rs->poll = device_property_read_bool(&pdev->dev, "rockchip,poll-only"); // Check SPI version and set cs_inactive switch (rs->version) { case ROCKCHIP_SPI_VER2_TYPE2: rs->cs_high_supported = true; ctlr->mode_bits |= SPI_CS_HIGH; if (slave_mode) rs->cs_inactive = true; else rs->cs_inactive = false; break; default: rs->cs_inactive = false; break; } // Get pin control pinctrl = devm_pinctrl_get(&pdev->dev); if (!IS_ERR(pinctrl)) { rs->high_speed_state = pinctrl_lookup_state(pinctrl, "high_speed"); if (IS_ERR_OR_NULL(rs->high_speed_state)) { dev_warn(&pdev->dev, "no high_speed pinctrl state\n"); rs->high_speed_state = NULL; } } // Register SPI controller ret = devm_spi_register_controller(&pdev->dev, ctlr); if (ret < 0) { dev_err(&pdev->dev, "Failed to register controller\n"); goto err_free_dma_rx; } if (IS_ENABLED(CONFIG_SPI_ROCKCHIP_MISCDEV)) { char misc_name[20]; snprintf(misc_name, sizeof(misc_name), "rkspi-dev%d", ctlr->bus_num); rs->miscdev.minor = MISC_DYNAMIC_MINOR; rs->miscdev.name = misc_name; rs->miscdev.fops = &rockchip_spi_misc_fops; rs->miscdev.parent = &pdev->dev; ret = misc_register(&rs->miscdev); if (ret) dev_err(&pdev->dev, "failed to register misc device %s\n", misc_name); else dev_info(&pdev->dev, "register misc device %s\n", misc_name); } dev_info(rs->dev, "probed, poll=%d, rsd=%d\n", rs->poll, rs->rsd); return 0;err_free_dma_rx: if (ctlr->dma_rx) dma_release_channel(ctlr->dma_rx);err_free_dma_tx: if (ctlr->dma_tx) dma_release_channel(ctlr->dma_tx);err_disable_pm_runtime: pm_runtime_disable(&pdev->dev);err_disable_sclk_in: clk_disable_unprepare(rs->sclk_in);err_disable_spiclk: clk_disable_unprepare(rs->spiclk);err_disable_apbclk: clk_disable_unprepare(rs->apb_pclk);err_put_ctlr: spi_controller_put(ctlr); return ret;} |
devm_spi_register_controller()
1234567891011121314151617181920212223242526272829303132333435 | /** * devm_spi_register_controller - register managed SPI master or slave * controller * @dev: device managing SPI controller * @ctlr: initialized controller, originally from spi_alloc_master() or * spi_alloc_slave() * Context: can sleep * * Register a SPI device as with spi_register_controller() which will * automatically be unregistered and freed. * * Return: zero on success, else a negative error code. */int devm_spi_register_controller(struct device *dev, struct spi_controller *ctlr){ struct spi_controller **ptr; int ret; // Allocate devres memory to automatically release controller resources ptr = devres_alloc(devm_spi_unregister, sizeof(*ptr), GFP_KERNEL); if (!ptr) return -ENOMEM;// Memory allocation failed // Register SPI controller ret = spi_register_controller(ctlr); if (!ret) { *ptr = ctlr;// On successful registration, store the pointer in the device resource manager devres_add(dev, ptr); } else { devres_free(ptr);// Registration failed, release the allocated resources. } return ret;// Return the registration result.}EXPORT_SYMBOL_GPL(devm_spi_register_controller); |
spi_register_controller()
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 | int spi_register_controller(struct spi_controller *ctlr){ struct device *dev = ctlr->dev.parent; struct boardinfo *bi; int status; int id, first_dynamic; if (!dev) return -ENODEV; /* * Make sure all necessary hooks are implemented before registering * the SPI controller. */ /* * When registering SPI before the controller,Ensure that all necessary operations have been implemented. */ status = spi_controller_check_ops(ctlr); if (status) return status; if (ctlr->bus_num >= 0) { /* devices with a fixed bus num must check-in with the num */ mutex_lock(&board_lock);/* Devices with a fixed bus number must be checked using that number. */ id = idr_alloc(&spi_master_idr, ctlr, ctlr->bus_num, ctlr->bus_num + 1, GFP_KERNEL); mutex_unlock(&board_lock); if (WARN(id < 0, "couldn't get idr")) return id == -ENOSPC ? -EBUSY : id; ctlr->bus_num = id; } else if (ctlr->dev.of_node) { /* allocate dynamic bus number using Linux idr */ id = of_alias_get_id(ctlr->dev.of_node, "spi"); if (id >= 0) { ctlr->bus_num = id; mutex_lock(&board_lock); id = idr_alloc(&spi_master_idr, ctlr, ctlr->bus_num, ctlr->bus_num + 1, GFP_KERNEL); mutex_unlock(&board_lock); if (WARN(id < 0, "couldn't get idr")) return id == -ENOSPC ? -EBUSY : id; } } if (ctlr->bus_num < 0) { first_dynamic = of_alias_get_highest_id("spi"); if (first_dynamic < 0) first_dynamic = 0; else first_dynamic++; mutex_lock(&board_lock); id = idr_alloc(&spi_master_idr, ctlr, first_dynamic, 0, GFP_KERNEL); mutex_unlock(&board_lock); if (WARN(id < 0, "couldn't get idr")) return id; ctlr->bus_num = id; } INIT_LIST_HEAD(&ctlr->queue); spin_lock_init(&ctlr->queue_lock); spin_lock_init(&ctlr->bus_lock_spinlock); mutex_init(&ctlr->bus_lock_mutex); mutex_init(&ctlr->io_mutex); ctlr->bus_lock_flag = 0; init_completion(&ctlr->xfer_completion); if (!ctlr->max_dma_len) ctlr->max_dma_len = INT_MAX; /* register the device, then userspace will see it. * registration fails if the bus ID is in use. */ dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num); if (!spi_controller_is_slave(ctlr)) { if (ctlr->use_gpio_descriptors) { status = spi_get_gpio_descs(ctlr); if (status) goto free_bus_id; /* * A controller using GPIO descriptors always * supports SPI_CS_HIGH if need be. */ ctlr->mode_bits |= SPI_CS_HIGH; } else { /* Legacy code path for GPIOs from DT */ status = of_spi_get_gpio_numbers(ctlr); if (status) goto free_bus_id; } } /* * Even if it's just one always-selected device, there must * be at least one chipselect. */ if (!ctlr->num_chipselect) { status = -EINVAL; goto free_bus_id; } status = device_add(&ctlr->dev); if (status < 0) goto free_bus_id; dev_dbg(dev, "registered %s %s\n", spi_controller_is_slave(ctlr) ? "slave" : "master", dev_name(&ctlr->dev)); /* * If we're using a queued driver, start the queue. Note that we don't * need the queueing logic if the driver is only supporting high-level * memory operations. */ if (ctlr->transfer) { dev_info(dev, "controller is unqueued, this is deprecated\n"); } else if (ctlr->transfer_one || ctlr->transfer_one_message) { status = spi_controller_initialize_queue(ctlr); if (status) { device_del(&ctlr->dev); goto free_bus_id; } } /* add statistics */ spin_lock_init(&ctlr->statistics.lock); mutex_lock(&board_lock); list_add_tail(&ctlr->list, &spi_controller_list); list_for_each_entry(bi, &board_list, list) spi_match_controller_to_boardinfo(ctlr, &bi->board_info); mutex_unlock(&board_lock); /* Register devices from the device tree and ACPI */ of_register_spi_devices(ctlr); acpi_register_spi_devices(ctlr); return status;free_bus_id: mutex_lock(&board_lock); idr_remove(&spi_master_idr, ctlr->bus_num); mutex_unlock(&board_lock); return status;}EXPORT_SYMBOL_GPL(spi_register_controller); |
In line 132 of this function, the device tree resource registration function is called.of_register_spi_devicesRegister the SPI child node device tree. The specific content of this function is as follows:
of_register_spi_devices()
123456789101112131415161718192021222324252627 | /** * of_register_spi_devices() - Register child devices onto the SPI bus * @ctlr: Pointer to spi_controller device * * Registers an spi_device for each child node of controller node which * represents a valid SPI slave. */static void of_register_spi_devices(struct spi_controller *ctlr){ struct spi_device *spi; struct device_node *nc; if (!ctlr->dev.of_node)// If the controller has no device tree node, return directly. return; // Traverse each child node under the controller device tree node. for_each_available_child_of_node(ctlr->dev.of_node, nc) { if (of_node_test_and_set_flag(nc, OF_POPULATED))// If the node has been marked as filled, skip the node. continue; spi = of_register_spi_device(ctlr, nc);// Register an SPI device for the node. if (IS_ERR(spi)) {// If registration fails, log a warning message and clear the node's filled flag. dev_warn(&ctlr->dev, "Failed to create SPI device for %pOF\n", nc); of_node_clear_flag(nc, OF_POPULATED); } }} |
Line 17 traverses each child node under the controller device tree node, throughof_register_spi_devicefunction registers the SPI child node,of_register_spi_deviceThe specific content of the function is as follows:
of_register_spi_device()
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 | static struct spi_device *of_register_spi_device(struct spi_controller *ctlr, struct device_node *nc){ struct spi_device *spi; int rc; /* Alloc an spi_device */ spi = spi_alloc_device(ctlr);/* Allocate an spi_device. */ if (!spi) { dev_err(&ctlr->dev, "spi_device alloc error for %pOF\n", nc); rc = -ENOMEM; goto err_out; } /* Select device driver */ /* Select the device driver. */ rc = of_modalias_node(nc, spi->modalias, sizeof(spi->modalias)); if (rc < 0) { dev_err(&ctlr->dev, "cannot find modalias for %pOF\n", nc); goto err_out; } rc = of_spi_parse_dt(ctlr, spi, nc);/* Parse the SPI information in the device tree. */ if (rc) goto err_out; /* Store a pointer to the node in the device structure */ of_node_get(nc);/* Store the pointer to the node in the device structure. */ spi->dev.of_node = nc; spi->dev.fwnode = of_fwnode_handle(nc); /* Register the new device */ rc = spi_add_device(spi);/* Register the new device. */ if (rc) { dev_err(&ctlr->dev, "spi_device register error %pOF\n", nc); goto err_of_node_put; } return spi;err_of_node_put: of_node_put(nc);err_out: spi_dev_put(spi); return ERR_PTR(rc);} |
Line 24 callsof_spi_parse_dtfunction to parse the SPI information in the device tree child node,of_spi_parse_dtThe specific content of the function is as follows
of_spi_parse_dt()
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 | static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi, struct device_node *nc){ u32 value; int rc; /* Mode (clock phase/polarity/etc.) */ /* 设置模式 (时钟相位/极性等) */ if (of_property_read_bool(nc, "spi-cpha")) spi->mode |= SPI_CPHA; if (of_property_read_bool(nc, "spi-cpol")) spi->mode |= SPI_CPOL; if (of_property_read_bool(nc, "spi-3wire")) spi->mode |= SPI_3WIRE; if (of_property_read_bool(nc, "spi-lsb-first")) spi->mode |= SPI_LSB_FIRST; if (of_property_read_bool(nc, "spi-cs-high")) spi->mode |= SPI_CS_HIGH; /* Device DUAL/QUAD mode */ /* Set the DUAL/QUAD mode of the device. */ if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) { switch (value) { case 1: break; case 2: spi->mode |= SPI_TX_DUAL; break; case 4: spi->mode |= SPI_TX_QUAD; break; case 8: spi->mode |= SPI_TX_OCTAL; break; default: dev_warn(&ctlr->dev, "spi-tx-bus-width %d not supported\n", value); break; } } if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) { switch (value) { case 1: break; case 2: spi->mode |= SPI_RX_DUAL; break; case 4: spi->mode |= SPI_RX_QUAD; break; case 8: spi->mode |= SPI_RX_OCTAL; break; default: dev_warn(&ctlr->dev, "spi-rx-bus-width %d not supported\n", value); break; } } /* If it is an SPI slave device */ if (spi_controller_is_slave(ctlr)) { if (!of_node_name_eq(nc, "slave")) { dev_err(&ctlr->dev, "%pOF is not called 'slave'\n", nc); return -EINVAL; } return 0; } /* Device address */ rc = of_property_read_u32(nc, "reg", &value);/* Get device address */ if (rc) { dev_err(&ctlr->dev, "%pOF has no valid 'reg' property (%d)\n", nc, rc); return rc; } spi->chip_select = value; /* Device speed */ if (!of_property_read_u32(nc, "spi-max-frequency", &value))/* Get device speed */ spi->max_speed_hz = value; return 0;} |
If the device tree does not existregandspi-max-frequencyThe two properties will return rc, which causes the upper-level functionof_register_spi_deviceto return an error, preventing successful registration of the SPI device and parsing of the device tree node, ultimately causing the written SPI device driver to fail to match properly.
example
12345678910111213141516171819202122232425262728293031323334353637383940414243 | int mcp2515_probe(struct spi_device *spi){ pr_info("%s\n", __func__); return 0;}int mcp2515_remove(struct spi_device *spi){ return 0;}static const struct of_device_id mcp2515_match_table[] = { {.compatible = "my-mcp2515"}, {}};MODULE_DEVICE_TABLE(of, mcp2515_match_table);static const struct spi_device_id mcp2515_id_table[] = { {.name = "mcp2515"}, {}};MODULE_DEVICE_TABLE(spi, mcp2515_id_table);static struct spi_driver spi_mcp2515 = { .probe = mcp2515_probe, .remove = mcp2515_remove, .driver = { .name = "mcp2515", .owner = THIS_MODULE, .of_match_table = mcp2515_match_table, }, .id_table = mcp2515_id_table,};module_spi_driver(spi_mcp2515);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629 <asqwgo@outlook.com>");MODULE_DESCRIPTION("This is test sample for mcp2515 spi driver"); |
Character device registration example
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 | dev_t dev_num; // Device IDstruct cdev mcp2515_cdev; // Character device structurestruct class *mcp2515_class; // Device classstruct device *mcp2515_device; // devicestruct spi_device *spi_dev; // SPI device pointer// MCP2515 chip reset functionvoid mcp2515_reset(void){ int ret; char write_buf[] = {0xc0}; // Reset command 0xc0 (binary 11000000) ret = spi_write(spi_dev, write_buf, sizeof(write_buf)); // Send reset command if(ret < 0){ printk("spi_write is error\n"); // Print error message }}// MCP2515 read register functionchar mcp2515_read_reg(char reg) { char write_buf[] = {0x03, reg}; // Write SPI read command 0x03 to the SPI write buffer char read_buf; // SPI read buffer int ret; ret = spi_write_then_read(spi_dev, write_buf, sizeof(write_buf), &read_buf, sizeof(read_buf)); // Call SPI write-read function if (ret < 0) { printk("spi_write_then_read error\n"); return ret; } return read_buf;}// Open device file callback functionint mcp2515_open(struct inode *inode, struct file *file) { return 0; // Return success}// Callback function for reading the device filessize_t mcp2515_read(struct file *file, char __user *buf, size_t size, loff_t *offset) { return 0; // Return success}// Callback function for writing to the device filessize_t mcp2515_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { return 0; // Return success}// Callback function for closing the device fileint mcp2515_release(struct inode *inode, struct file *file) { return 0; // Return success}// Device file operation setstruct file_operations mcp2515_fops = { .open = mcp2515_open, .read = mcp2515_read, .write = mcp2515_write, .release = mcp2515_release,};// MCP2515 device initialization functionint mcp2515_probe(struct spi_device *spi) { int ret; int value; printk("This is mcp2515_probe\n"); spi_dev = spi; // Save SPI device pointer // Allocate character device number ret = alloc_chrdev_region(&dev_num, 0, 1, "mcp2515"); if (ret < 0) { printk("alloc_chrdev_region error\n"); } // Initialize character device cdev_init(&mcp2515_cdev, &mcp2515_fops); mcp2515_cdev.owner = THIS_MODULE; // Add character device ret = cdev_add(&mcp2515_cdev, dev_num, 1); if (ret < 0) { printk("cdev_add error\n"); return -1; } // Create device class mcp2515_class = class_create(THIS_MODULE, "spi_to_can"); if (IS_ERR(mcp2515_class)) { printk("mcp2515_class error\n"); return PTR_ERR(mcp2515_class); } // Create device mcp2515_device = device_create(mcp2515_class, NULL, dev_num, NULL, "mcp2515"); if (IS_ERR(mcp2515_device)) { printk("mcp2515_device error\n"); return PTR_ERR(mcp2515_device); } mcp2515_reset(); // Reset MCP2515 device value = mcp2515_read_reg(0x0e); // Read register value printk("value is %x\n", value); // Print the read value return 0; // Return success}// MCP2515 SPI device removal functionstatic int mcp2515_remove(struct spi_device *spi) { device_destroy(mcp2515_class, dev_num); class_destroy(mcp2515_class); cdev_del(&mcp2515_cdev); unregister_chrdev_region(dev_num, 1); return 0;}// MCP2515 device match table, used for device tree matchingstatic const struct of_device_id mcp2515_of_match_table[] = { { .compatible = "my-mcp2515" }, {}};// MCP2515 device ID match table, used for bus matchingstatic const struct spi_device_id mcp2515_id_table[] = { { "mcp2515", 0 }, {}};// MCP2515 SPI driver structurestatic struct spi_driver spi_mcp2515 = { .probe = mcp2515_probe, // Probe function .remove = mcp2515_remove, // Remove function .driver = { .name = "mcp2515", // Driver name .owner = THIS_MODULE, // Owner module .of_match_table = mcp2515_of_match_table, // Device tree match table}, .id_table = mcp2515_id_table, // Device ID match table};// Driver initialization functionstatic int __init mcp2515_init(void){ int ret; // Register SPI driver ret = spi_register_driver(&spi_mcp2515); if (ret < 0) { // Print error message on registration failure printk("spi_register_driver error\n"); return ret; } return ret;}// Driver exit functionstatic void __exit mcp2515_exit(void){ // Unregister SPI driver spi_unregister_driver(&spi_mcp2515);}module_init(mcp2515_init);module_exit(mcp2515_exit);MODULE_LICENSE("GPL"); |
SPI communication flow
In Linux drivers, you can usespi_writefunction to send data to the SPI slave device,spi_writeThe function is defined ininclude/linux/spi/spi.hfile, and the specific content is as follows
spi_write()
12345678910111213141516171819202122 | /** * spi_write - SPI synchronous write * @spi: device to which data will be written * @buf: data buffer * @len: data buffer size * Context: can sleep * * This function writes the buffer @buf. * Callable only from contexts that can sleep. * * Return: zero on success, else a negative error code. */static inline intspi_write(struct spi_device *spi, const void *buf, size_t len){ struct spi_transfer t = { .tx_buf = buf, .len = len, }; return spi_sync_transfer(spi, &t, 1);} |
This function first encapsulates the data to be transmitted and the size of the data, then callsspi_sync_transferfunction to perform input transfer,spi_writeThe first parameter passed to the function isspi_devicea structure variable of type.
struct spi_deviceis a structure in the Linux kernel used to describe SPI slave devices. It contains various information and configuration options related to SPI devices. The specific contents of this structure are as follows:
struct spi_device
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 | /** * struct spi_device - Controller side proxy for an SPI slave device * @dev: Driver model representation of the device. * @controller: SPI controller used with the device. * @master: Copy of controller, for backwards compatibility. * @max_speed_hz: Maximum clock rate to be used with this chip * (on this board); may be changed by the device's driver. * The spi_transfer.speed_hz can override this for each transfer. * @chip_select: Chipselect, distinguishing chips handled by @controller. * @mode: The spi mode defines how data is clocked out and in. * This may be changed by the device's driver. * The "active low" default for chipselect mode can be overridden * (by specifying SPI_CS_HIGH) as can the "MSB first" default for * each word in a transfer (by specifying SPI_LSB_FIRST). * @bits_per_word: Data transfers involve one or more words; word sizes * like eight or 12 bits are common. In-memory wordsizes are * powers of two bytes (e.g. 20 bit samples use 32 bits). * This may be changed by the device's driver, or left at the * default (0) indicating protocol words are eight bit bytes. * The spi_transfer.bits_per_word can override this for each transfer. * @rt: Make the pump thread real time priority. * @irq: Negative, or the number passed to request_irq() to receive * interrupts from this device. * @controller_state: Controller's runtime state * @controller_data: Board-specific definitions for controller, such as * FIFO initialization parameters; from board_info.controller_data * @modalias: Name of the driver to use with this device, or an alias * for that name. This appears in the sysfs "modalias" attribute * for driver coldplugging, and in uevents used for hotplugging * @driver_override: If the name of a driver is written to this attribute, then * the device will bind to the named driver and only the named driver. * @cs_gpio: LEGACY: gpio number of the chipselect line (optional, -ENOENT when * not using a GPIO line) use cs_gpiod in new drivers by opting in on * the spi_master. * @cs_gpiod: gpio descriptor of the chipselect line (optional, NULL when * not using a GPIO line) * @word_delay: delay to be inserted between consecutive * words of a transfer * * @statistics: statistics for the spi_device * * A @spi_device is used to interchange data between an SPI slave * (usually a discrete chip) and CPU memory. * * In @dev, the platform_data is used to hold information about this * device that's meaningful to the device's protocol driver, but not * to its controller. One example might be an identifier for a chip * variant with slightly different functionality; another might be * information about how this particular board wires the chip's pins. */struct spi_device { struct device dev; // Device structure of the generic device model struct spi_controller *controller; // Pointer to the controller struct spi_controller *master; /* compatibility layer */ // Compatibility layer, pointer to the controller (same as controller) u32 max_speed_hz;// Maximum speed supported by the device (in hertz) u8 chip_select;// Chip select number u8 bits_per_word;// Number of bits per word bool rt; u32 mode;// SPI mode configuration (including clock phase and polarity, etc.) int irq; void *controller_state; // Private data for controller state void *controller_data; // Private data for controller data char modalias[SPI_NAME_SIZE];// Device alias const char *driver_override;// Driver override int cs_gpio; /* LEGACY: chip select gpio */ // Chip select GPIO pin struct gpio_desc *cs_gpiod; /* chip select gpio desc */ // Chip select GPIO pin struct spi_delay word_delay; /* the statistics */ struct spi_statistics statistics;// Statistics /* * likely need more hooks for more protocol options affecting how * the controller talks to each chip, like: * - memory packing (12 bit samples into low bits, others zeroed) * - priority * - chipselect delays * - ... */}; |
spi_writeThe function can send data to the SPI slave device, whilespi_readThe function can receive data sent from the device,spi_readThe specific content of the function is as follows:
spi_read()
12345678910111213141516171819202122 | /** * spi_read - SPI synchronous read * @spi: device from which data will be read * @buf: data buffer * @len: data buffer size * Context: can sleep * * This function reads the buffer @buf. * Callable only from contexts that can sleep. * * Return: zero on success, else a negative error code. */static inline intspi_read(struct spi_device *spi, void *buf, size_t len){ struct spi_transfer t = { .rx_buf = buf, .len = len, }; return spi_sync_transfer(spi, &t, 1);} |
Same asspi_writethe function,spi_readthe function also packetizes the data, packing the data buf and data size len intospi_transfera structure of type,struct spi_transferis a structure describing SPI data transmission, used to configure various parameters of an SPI data transfer. The specific content of this structure is as follows:
struct spi_transfer
12345678910111213141516171819202122232425262728293031323334353637383940414243 | struct spi_transfer { /* it's ok if tx_buf == rx_buf (right?) * for MicroWire, one buffer must be null * buffers must work with dma_*map_single() calls, unless * spi_message.is_dma_mapped reports a pre-existing mapping */ const void *tx_buf;// Transmit buffer void *rx_buf;// Receive buffer unsigned len;// Length of data to be transmitted dma_addr_t tx_dma;// DMA address of the transmit buffer dma_addr_t rx_dma;// DMA address of the receive buffer struct sg_table tx_sg;// Scatter-gather table of the transmit buffer struct sg_table rx_sg;// Scatter-gather table of the receive buffer unsigned cs_change:1;// Whether to change the chip select state after transmission unsigned tx_nbits:3;// Transmit bit width (single, dual, or quad-line transmission) unsigned rx_nbits:3;// Receive bit width (single, dual, or quad-line transmission) u8 bits_per_word;// Number of bits per word u16 delay_usecs;// Delay between transfers (microseconds) struct spi_delay delay; struct spi_delay cs_change_delay; struct spi_delay word_delay;// Delay between each word u32 speed_hz;// Transfer speed (Hz) u32 effective_speed_hz; unsigned int ptp_sts_word_pre; unsigned int ptp_sts_word_post; struct ptp_system_timestamp *ptp_sts; bool timestamped; struct list_head transfer_list; u16 error;}; |
spi_sync_transfer()
Andspi_writeSum of functionsspi_readdiffer only instruct spi_transferthe difference in structure parameters, and are encapsulated asstruct spi_transferafter which further encapsulation is required,spi_writeSum of functionsspi_readThe function will eventually be called.spi_sync_transferFunction, the specific content of which is as follows:
123456789101112131415161718192021222324 | /** * spi_sync_transfer - synchronous SPI data transfer * @spi: device with which data will be exchanged * @xfers: An array of spi_transfers * @num_xfers: Number of items in the xfer array * Context: can sleep * * Does a synchronous SPI data transfer of the given spi_transfer array. * * For more specific semantics see spi_sync(). * * Return: zero on success, else a negative error code. */static inline intspi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers){ struct spi_message msg; // Initialize the SPI message using the given transfer. spi_message_init_with_transfers(&msg, xfers, num_xfers); // Send SPI message synchronously return spi_sync(spi, &msg);} |
This function is mainly used to encapsulate SPI synchronous transmission operations, simplifying the calling process. It callsspi_message_init_with_transfersThe function initializes SPI data transmission and finally callsspi_syncThe function sends SPI data synchronously,spi_syncThe specific content of the function is as follows:
spi_sync()
12345678910111213141516171819202122232425262728293031323334353637 | /** * spi_sync - blocking/synchronous SPI data transfers * @spi: device with which data will be exchanged * @message: describes the data transfers * Context: can sleep * * This call may only be used from a context that may sleep. The sleep * is non-interruptible, and has no timeout. Low-overhead controller * drivers may DMA directly into and out of the message buffers. * * Note that the SPI device's chip select is active during the message, * and then is normally disabled between messages. Drivers for some * frequently-used devices may want to minimize costs of selecting a chip, * by leaving it selected in anticipation that the next message will go * to the same chip. (That may increase power usage.) * * Also, the caller is guaranteeing that the memory associated with the * message will not be freed before this call returns. * * Return: zero on success, else a negative error code. */int spi_sync(struct spi_device *spi, struct spi_message *message){ int ret; // Lock the SPI controller's bus lock mutex. mutex_lock(&spi->controller->bus_lock_mutex); // Perform synchronous SPI transfer ret = __spi_sync(spi, message); // Unlock the bus lock mutex of the SPI controller. mutex_unlock(&spi->controller->bus_lock_mutex); return ret;}EXPORT_SYMBOL_GPL(spi_sync); |
The main purpose of this function is to ensure that SPI data transfer operations are performed under the protection of a mutex lock, so as to avoid conflicts and data errors caused by concurrent transfers.
By calling the internal__spi_syncfunction to perform the actual data transfer.__spi_syncThe function is as follows.
__spi_sync()
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061 | static int __spi_sync(struct spi_device *spi, struct spi_message *message){ // Declare and initialize a completion variable. DECLARE_COMPLETION_ONSTACK(done); int status; struct spi_controller *ctlr = spi->controller; unsigned long flags; // Verify SPI device and message status = __spi_validate(spi, message); if (status != 0) return status; // Set message completion callback and context message->complete = spi_complete; message->context = &done; message->spi = spi; // Update statistics SPI_STATISTICS_INCREMENT_FIELD(&ctlr->statistics, spi_sync); SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync); /* If we're not using the legacy transfer method then we will * try to transfer in the calling context so special case. * This code would be less tricky if we could remove the * support for driver implemented message queues. */ if (ctlr->transfer == spi_queued_transfer) { spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);// Lock the bus lock and spin lock, and save the interrupt flag. trace_spi_message_submit(message);// Record tracking information for SPI message submission status = __spi_queued_transfer(spi, message, false);// Execute queue transfer spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);// Unlock the bus lock spinlock and restore the interrupt flag. } else { status = spi_async_locked(spi, message);// Asynchronous Locking Transmission } if (status == 0) { /* Push out the messages in the calling context if we * can. */ if (ctlr->transfer == spi_queued_transfer) {/* If possible, push the message in the calling context */ // Update statistics for synchronous immediate transfers SPI_STATISTICS_INCREMENT_FIELD(&ctlr->statistics, spi_sync_immediate); SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync_immediate); // Push message __spi_pump_messages(ctlr, false); } // Wait for completion wait_for_completion(&done); // Get the status of the message status = message->status; } // Clear the context of the message message->context = NULL; return status;} |
The main function of this function is to synchronously execute SPI message transmission in a locked context. It is responsible for initializing the transfer message, validating the message and device, handling the transfer, and returning the transfer status upon completion. The focus of this function is on__spi_pump_messagesPush message function, the specific content of which is as follows:
__spi_pump_messages()
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 | /** * __spi_pump_messages - function which processes spi message queue * @ctlr: controller to process queue for * @in_kthread: true if we are in the context of the message pump thread * * This function checks if there is any spi message in the queue that * needs processing and if so call out to the driver to initialize hardware * and transfer each message. * * Note that it is called both from the kthread itself and also from * inside spi_sync(); the queue extraction handling at the top of the * function should deal with this safely. */static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread){ struct spi_transfer *xfer; struct spi_message *msg; bool was_busy = false; unsigned long flags; int ret; /* Lock queue */ spin_lock_irqsave(&ctlr->queue_lock, flags);/* Lock the message queue */ /* Make sure we are not already running a message */ if (ctlr->cur_msg) {/* Ensure no other message is being processed */ spin_unlock_irqrestore(&ctlr->queue_lock, flags); return; } /* If another context is idling the device then defer */ if (ctlr->idling) {/* If another context is idling the device, defer processing */ kthread_queue_work(ctlr->kworker, &ctlr->pump_messages); spin_unlock_irqrestore(&ctlr->queue_lock, flags); return; } /* Check if the queue is idle */ if (list_empty(&ctlr->queue) || !ctlr->running) {/* Check whether the queue is idle */ if (!ctlr->busy) { spin_unlock_irqrestore(&ctlr->queue_lock, flags); return; } /* Defer any non-atomic teardown to the thread */ if (!in_kthread) {/* Only perform teardown operations in the thread */ if (!ctlr->dummy_rx && !ctlr->dummy_tx && !ctlr->unprepare_transfer_hardware) { spi_idle_runtime_pm(ctlr); ctlr->busy = false; trace_spi_controller_idle(ctlr); } else { kthread_queue_work(ctlr->kworker, &ctlr->pump_messages); } spin_unlock_irqrestore(&ctlr->queue_lock, flags); return; } ctlr->busy = false; ctlr->idling = true; spin_unlock_irqrestore(&ctlr->queue_lock, flags); kfree(ctlr->dummy_rx); ctlr->dummy_rx = NULL; kfree(ctlr->dummy_tx); ctlr->dummy_tx = NULL; if (ctlr->unprepare_transfer_hardware && ctlr->unprepare_transfer_hardware(ctlr)) dev_err(&ctlr->dev, "failed to unprepare transfer hardware\n"); spi_idle_runtime_pm(ctlr); trace_spi_controller_idle(ctlr); spin_lock_irqsave(&ctlr->queue_lock, flags); ctlr->idling = false; spin_unlock_irqrestore(&ctlr->queue_lock, flags); return; } /* Extract head of queue */ msg = list_first_entry(&ctlr->queue, struct spi_message, queue);/* Get the first message from the queue */ ctlr->cur_msg = msg; list_del_init(&msg->queue); if (ctlr->busy) was_busy = true; else ctlr->busy = true; spin_unlock_irqrestore(&ctlr->queue_lock, flags); mutex_lock(&ctlr->io_mutex); if (!was_busy && ctlr->auto_runtime_pm) { ret = pm_runtime_get_sync(ctlr->dev.parent); if (ret < 0) { pm_runtime_put_noidle(ctlr->dev.parent); dev_err(&ctlr->dev, "Failed to power device: %d\n", ret); mutex_unlock(&ctlr->io_mutex); return; } } if (!was_busy) trace_spi_controller_busy(ctlr); if (!was_busy && ctlr->prepare_transfer_hardware) { ret = ctlr->prepare_transfer_hardware(ctlr); if (ret) { dev_err(&ctlr->dev, "failed to prepare transfer hardware: %d\n", ret); if (ctlr->auto_runtime_pm) pm_runtime_put(ctlr->dev.parent); msg->status = ret; spi_finalize_current_message(ctlr); mutex_unlock(&ctlr->io_mutex); return; } } trace_spi_message_start(msg); if (ctlr->prepare_message) { ret = ctlr->prepare_message(ctlr, msg); if (ret) { dev_err(&ctlr->dev, "failed to prepare message: %d\n", ret); msg->status = ret; spi_finalize_current_message(ctlr); goto out; } ctlr->cur_msg_prepared = true; } ret = spi_map_msg(ctlr, msg); if (ret) { msg->status = ret; spi_finalize_current_message(ctlr); goto out; } if (!ctlr->ptp_sts_supported && !ctlr->transfer_one) { list_for_each_entry(xfer, &msg->transfers, transfer_list) { xfer->ptp_sts_word_pre = 0; ptp_read_system_prets(xfer->ptp_sts); } } ret = ctlr->transfer_one_message(ctlr, msg); if (ret) { dev_err(&ctlr->dev, "failed to transfer one message from queue\n"); goto out; }out: mutex_unlock(&ctlr->io_mutex); /* Prod the scheduler in case transfer_one() was busy waiting */ if (!ret)/* If the transfer succeeds, wake up the scheduler * cond_resched();} |
Codectlr->transfer_one_messageis a function pointer that points to the function in the SPI controller responsible for executing SPI message transfers. By calling this function, the current SPI message isctlr->cur_msgpassed to that function for processing. This function is usually responsible for sending the message data to the SPI device or receiving data from the device, and communicating with the hardware device.
Therefore,ctlr->transfer_one_message(ctlr, ctlr->cur_msg)The purpose of this line of code is to pass the current SPI message to the transfer function in the SPI controller for processing, so as to complete the message transfer operation.
spi_write_then_read()
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 | /** * spi_write_then_read - SPI synchronous write followed by read * @spi: device with which data will be exchanged * @txbuf: data to be written (need not be dma-safe) * @n_tx: size of txbuf, in bytes * @rxbuf: buffer into which data will be read (need not be dma-safe) * @n_rx: size of rxbuf, in bytes * Context: can sleep * * This performs a half duplex MicroWire style transaction with the * device, sending txbuf and then reading rxbuf. The return value * is zero for success, else a negative errno status code. * This call may only be used from a context that may sleep. * * Parameters to this routine are always copied using a small buffer. * Performance-sensitive or bulk transfer code should instead use * spi_{async,sync}() calls with dma-safe buffers. * * Return: zero on success, else a negative error code. */int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx){ static DEFINE_MUTEX(lock);// Define a static mutex int status; struct spi_message message; struct spi_transfer x[2]; u8 *local_buf; /* Use preallocated DMA-safe buffer if we can. We can't avoid * copying here, (as a pure convenience thing), but we can * keep heap costs out of the hot path unless someone else is * using the pre-allocated buffer or the transfer is too large. */ if ((n_tx + n_rx) > SPI_BUFSIZ || !mutex_trylock(&lock)) { local_buf = kmalloc(max((unsigned)SPI_BUFSIZ, n_tx + n_rx), GFP_KERNEL | GFP_DMA); if (!local_buf) return -ENOMEM; // If memory allocation fails, return an error code } else { local_buf = buf; } spi_message_init(&message);// Initialize SPI message memset(x, 0, sizeof(x)); if (n_tx) { x[0].len = n_tx; spi_message_add_tail(&x[0], &message); } if (n_rx) { x[1].len = n_rx; spi_message_add_tail(&x[1], &message); } memcpy(local_buf, txbuf, n_tx);// Copy the data to be sent to a local buffer x[0].tx_buf = local_buf; x[1].rx_buf = local_buf + n_tx; /* do the i/o */ status = spi_sync(spi, &message);/* Perform I/O operation */ if (status == 0) memcpy(rxbuf, x[1].rx_buf, n_rx);// If the transfer succeeds, copy the received data to the receive buffer if (x[0].tx_buf == buf)// Release lock or free memory mutex_unlock(&lock); else kfree(local_buf); return status;// Return transfer status}EXPORT_SYMBOL_GPL(spi_write_then_read); |
MCP2515 driver development
MCP2515 has five modes, namely:
- Configuration Mode
- Normal Mode
- Sleep Mode
- Listen-Only Mode
- Loopback Mode

11000000. Example
12345678910 | struct spi_device *spi_dev; // SPI device pointer// MCP2515 chip reset functionvoid mcp2515_reset(void){ int ret; char write_buf[] = {0xc0}; // Reset command 0xc0 (binary 11000000) ret = spi_write(spi_dev, write_buf, sizeof(write_buf)); // Send reset command if(ret < 0){ printk("spi_write is error\n"); // Print error message }} |
The complete driver is as follows:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 | struct mcp2515_drv_data { dev_t dev_num; // Device ID struct cdev mcp2515_cdev; // Character device structure struct class *mcp2515_class; // Device class struct device *mcp2515_device; // device struct spi_device *spi_dev; // SPI device pointer};// MCP2515 chip reset functionvoid mcp2515_reset(struct spi_device *spi_dev){ int ret; char write_buf[] = { 0xc0 }; // Reset command 0xc0 (binary 11000000) ret = spi_write(spi_dev, write_buf, sizeof(write_buf)); // Send reset command if (ret < 0) { printk("spi_write is error\n"); // Print error message }}// MCP2515 read register functionchar mcp2515_read_reg(struct spi_device *spi_dev, char reg){ char write_buf[] = { 0x03, reg }; // Write SPI read command 0x03 to the SPI write buffer char read_buf; // SPI read buffer int ret; ret = spi_write_then_read(spi_dev, write_buf, sizeof(write_buf), &read_buf, sizeof(read_buf)); // Call SPI write-read function if (ret < 0) { printk("spi_write_then_read error\n"); return ret; } return read_buf;}// MCP2515 write register functionvoid mcp2515_write_reg(struct spi_device *spi_dev, char reg, char value){ int ret; char write_buf[] = { 0x02, reg, value }; // SPI write buffer, used to send write register command ret = spi_write(spi_dev, write_buf, sizeof(write_buf)); // Send SPI write command if (ret < 0) { printk("mcp2515_write_reg error\n"); }}// MCP2515 modify register bit functionvoid mcp2515_change_regbit(struct spi_device *spi_dev, char reg, char mask, char value){ int ret; char write_buf[] = { 0x05, reg, mask, value }; // SPI write buffer, used to send modify register bit command ret = spi_write(spi_dev, write_buf, sizeof(write_buf)); // Send SPI write command if (ret < 0) { printk("mcp2515_change_regbit error\n"); }}// Open device file callback functionint mcp2515_open(struct inode *inode, struct file *file){ struct mcp2515_drv_data *drv_data = container_of(inode->i_cdev, struct mcp2515_drv_data, mcp2515_cdev); file->private_data = drv_data; return 0; // Return success}// Read device operation function, reads data from device into user bufferssize_t mcp2515_read(struct file *file, char __user *buf, size_t size, loff_t *offset){ char r_kbuf[13] = { 0 }; // Kernel buffer, used to store data read from device int i; int ret; struct mcp2515_drv_data *drv_data = file->private_data; struct spi_device *spi_dev = drv_data->spi_dev; // Wait for the receive buffer full flag to be set while (!(mcp2515_read_reg(spi_dev, CANINTF) & (1 << 0))) ; // Read data from receive buffer into kernel buffer for (i = 0; i < sizeof(r_kbuf); i++) { r_kbuf[i] = mcp2515_read_reg(spi_dev, 0x61 + i); } // Clear receive buffer full flag mcp2515_change_regbit(spi_dev, CANINTF, 0x01, 0x00); // Copy data from kernel buffer to user buffer ret = copy_to_user(buf, r_kbuf, size); if (ret) { printk("copy_to_user r_kbuf is error\n"); return -1; // Return -1 indicates failure to copy data } return 0; // Return 0 indicates successful data read}// Write device operation functionssize_t mcp2515_write(struct file *file, const char __user *buf, size_t size, loff_t *offset){ char w_kbuf[13] = { 0 }; int ret; int i; struct mcp2515_drv_data *drv_data = file->private_data; struct spi_device *spi_dev = drv_data->spi_dev; // Set some bits of the TXB0CTRL register mcp2515_change_regbit(spi_dev, TXB0CTRL, 0x03, 0x03); // Copy data from user space to kernel buffer ret = copy_from_user(w_kbuf, buf, size); if (ret) { printk("copy_from_user w_kbuf is error\n"); return -1; } // Write data to MCP2515 register for (i = 0; i < sizeof(w_kbuf); i++) { mcp2515_write_reg(spi_dev, 0x31 + i, w_kbuf[i]); } // Set some bits of the TXB0CTRL register to start transmission mcp2515_change_regbit(spi_dev, TXB0CTRL, 0x08, 0x08); // Wait for transmission to complete while (!(mcp2515_read_reg(spi_dev, CANINTF) & (1 << 2))) ; // Clear the transmission complete flag mcp2515_change_regbit(spi_dev, CANINTF, 0x04, 0x00); return size;}// Callback function for closing the device fileint mcp2515_release(struct inode *inode, struct file *file){ return 0; // Return success}// Device file operation setstruct file_operations mcp2515_fops = { .open = mcp2515_open, .read = mcp2515_read, .write = mcp2515_write, .release = mcp2515_release, .owner = THIS_MODULE,};// MCP2515 device initialization functionint mcp2515_probe(struct spi_device *spi){ int ret, value; struct mcp2515_drv_data *drv_data; drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL); if (!drv_data) { ret = -ENOMEM; goto kzalloc_err; } spi_set_drvdata(spi, drv_data); drv_data->spi_dev = spi; // Save SPI device pointer // Allocate character device number ret = alloc_chrdev_region(&drv_data->dev_num, 0, 1, "mcp2515"); if (ret < 0) { pr_err("alloc_chrdev_region error\n"); goto alloc_chrdev_err; } // Initialize character device cdev_init(&drv_data->mcp2515_cdev, &mcp2515_fops); drv_data->mcp2515_cdev.owner = THIS_MODULE; // Add character device ret = cdev_add(&drv_data->mcp2515_cdev, drv_data->dev_num, 1); if (ret < 0) { pr_err("cdev_add error\n"); goto cdev_add_err; } // Create device class drv_data->mcp2515_class = class_create(THIS_MODULE, "spi_to_can"); if (IS_ERR(drv_data->mcp2515_class)) { pr_err("mcp2515_class error\n"); ret = PTR_ERR(drv_data->mcp2515_class); goto class_create_err; } // Create device drv_data->mcp2515_device = device_create(drv_data->mcp2515_class, NULL, drv_data->dev_num, NULL, "mcp2515"); if (IS_ERR(drv_data->mcp2515_device)) { pr_err("mcp2515_device error\n"); ret = PTR_ERR(drv_data->mcp2515_device); goto device_create_err; } mcp2515_reset(spi); // Reset MCP2515 device value = mcp2515_read_reg(spi, 0x0e); // Read register value pr_info("value is %x\n", value); // Print the read value mcp2515_write_reg(spi, CNF1, 0x01); // Write register configuration value mcp2515_write_reg(spi, CNF2, 0xb1); mcp2515_write_reg(spi, CNF3, 0x05); mcp2515_write_reg(spi, RXB0CTRL, 0x60); mcp2515_write_reg(spi, CANINTE, 0x05); mcp2515_change_regbit(spi, CANCTRL, 0xe0, 0x40); value = mcp2515_read_reg(spi, 0x0e); // Read register value pr_info("value is %x\n", value); // Print the read value return 0; // Return successdevice_create_err: class_destroy(drv_data->mcp2515_class);class_create_err: cdev_del(&drv_data->mcp2515_cdev);cdev_add_err: unregister_chrdev_region(drv_data->dev_num, 1);alloc_chrdev_err: kfree(drv_data);kzalloc_err: return ret;}// MCP2515 SPI device removal functionstatic int mcp2515_remove(struct spi_device *spi){ struct mcp2515_drv_data *drv_data = spi_get_drvdata(spi); device_destroy(drv_data->mcp2515_class, drv_data->dev_num); class_destroy(drv_data->mcp2515_class); cdev_del(&drv_data->mcp2515_cdev); unregister_chrdev_region(drv_data->dev_num, 1); kfree(drv_data); return 0;}// MCP2515 device match table, used for device tree matchingstatic const struct of_device_id mcp2515_of_match_table[] = { { .compatible = "my-mcp2515" }, {} };MODULE_DEVICE_TABLE(of, mcp2515_of_match_table);// MCP2515 device ID match table, used for bus matchingstatic const struct spi_device_id mcp2515_id_table[] = { { "mcp2515", 0 }, {} };MODULE_DEVICE_TABLE(spi, mcp2515_id_table);// MCP2515 SPI driver structurestatic struct spi_driver spi_mcp2515 = { .probe = mcp2515_probe, // Probe function .remove = mcp2515_remove, // Remove function .driver = { .name = "mcp2515", // Driver name .owner = THIS_MODULE, // Owner module .of_match_table = mcp2515_of_match_table, // Device tree match table }, .id_table = mcp2515_id_table, // Device ID match table};// Driver initialization functionmodule_spi_driver(spi_mcp2515);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629 <asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for spi driver"); |
Linux generic SPI device driver
Similar to I2C devices, the Linux kernel also has a generic SPI device driver. The menuconfig path is as follows:
123 | > Device Drivers > SPI support [] User mode SPI device driver support |
In addition to kernel support, the device tree also needs to be modified. Since SPI0 has already been enabled, directly modify the mcp2515 device tree node written earlier. The modified mcp2515 node is shown below:rockchip,spidev
123456789101112 | &spi0 { status = "okay"; pinctrl-0 = <&spi0m1_cs0 &spi0m1_pins>; pinctrl-1 = <&spi0m1_cs0 &spi0m1_pins_hs>; mcp2515:mcp2515@0 { compatible = "rockchip,spidev"; reg = <0>; spi-max-frequency = <10000000>; status = "okay"; };} |
After the development board boots, if the /dev/spidev0.0 device node exists, it proves that the device tree and kernel configuration are correct.
/dev/spidev0.0It represents a specific device on an SPI bus. 0.0 is an identifier used to distinguish different SPI controllers and devices in the system. This identifier consists of two parts:
- The first number 0: indicates the SPI bus number. A system may have multiple SPI controllers, each corresponding to a bus number starting from 0.
- The second number 0: indicates the specific device number connected to the SPI bus. Multiple devices can be connected to one SPI bus, and each device is distinguished by the chip select signal (Chip Select, CS). Device numbers start from 0.
spidev_test tool
spidev_testIt is a command-line tool for testing and debugging SPI devices, usually used on Linux systems. It allows users to communicate directly with devices through the SPI bus, send data, and receive responses from devices.spidev_testThe source code is located in the topeet Linux source code’skernel/tools/spidirectory, and compilation requires cross-compilation.
12345 | makeCC=/home/topeet/Linux/linux_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gccLD=/home/topeet/Linux/linux_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-ld |
Basic Introduction: spidev_test is a user-space tool for testing and verifying SPI device drivers in Linux. It uses the spidev interface to communicate with SPI devices. This tool is mainly used to check whether SPI devices work properly and to perform basic read/write operations on SPI devices.
Main options and parameters:
-D /dev/spidevX.Y: Specify the SPI device node to be tested.-s <speed>: Set the SPI clock frequency (in Hz), for example -s 1000000 means 1 MHz.-d <delay>: Set the delay time between data transfers (in microseconds).-b <bits per word>: Set the number of bits per data word, usually 8 or 16.-H: Display transmitted data in hexadecimal mode.
Example operations
- Read device information:
spidev_test -D /dev/spidevX.Y -s 1000000

Read device information This reads data from the SPI device at a clock frequency of 1 MHz, displayed in hexadecimal by default.
- Write and read data:
spidev_test -D /dev/spidevX.Y -s 1000000 -b 8 -d 1000 -H -p 'hello'

Write and read data This command writes the string ‘hello’ to the SPI device and displays the device’s response data in hexadecimal mode.
-b 8Specify the number of bits per word as 8,-d 1000Set a delay of 1000 microseconds.- Read device information:
Continuous transfer:
spidev_test -D /dev/spidevX.Y -s 1000000 -b 8 -p 'abcdefgh'
Continuous transfer
This example continuously sends the bytes ‘abcdefgh’ to the SPI device.
spidev_fdx tool
Basic Introduction: spidev_fdx is a command-line tool for full-duplex SPI communication testing, mainly used for bidirectional data transmission and testing with SPI devices on Linux systems.
Main options and parameters
-D /dev/spidevX.Y: Specify the SPI device node to be tested.-s <speed>: Set the SPI clock frequency (in Hz), for example -s 1000000 means 1 MHz.-w <write_data>: Specify the data to be written to the SPI device, which can be a string in hexadecimal or ASCII format.-r <read_size>: Specify the size of data to read from the SPI device (in bytes).-b <bits per word>: Set the number of bits per data word, usually 8 or 16.-d <delay>: Set the delay time between data transfers (in microseconds).
Example operations
Sending and receiving data:
spidev_fdx -D /dev/spidevX.Y -s 1000000 -w 'hello' -r 5This writes the string ‘hello’ to the SPI device and reads 5 bytes of response data from the device.
Set clock frequency and delay:
spidev_fdx -D /dev/spidevX.Y -s 500000 -d 200 -w 'abcdef' -r 10This example sets the SPI clock frequency to 500 kHz, the data write delay to 200 microseconds, writes the string ‘abcdef’ to the device, and then reads 10 bytes of response data.
How to use SPI in applications
You can refer tospidev_testthe source code of the tool
Using simulated SPI in Linux
First, compile the simulated SPI driver into the kernel, and select the following options in the make menuconfig graphical configuration interface:
123 | Device Drivers ---> [*]SPI support --> <*> GPIO-based bitbanging SPI Master //Selected |
The pins selected for software SPI are the 4 GPIOs on the back of the development board. The specific pin function diagram is as follows:
| RK3568 net label | Corresponding GPIO | Simulated function |
|---|---|---|
| DVP_PWREN0_H_GPIO0_B0 | GPIO0_B0 | SCLK |
| PDM_SDI3_M0_ADC | GPIO1_B0 | MISO |
| PDM_SDI2_M0_ADC | GPIO1_B1 | MOSI |
| PDM_SDI1_M0_ADC | GPIO1_B2 | CS |
The device tree modification steps are as follows:
123456789101112 | spi5: spi@gpiol { compatible = "spi-gpio"; gpio-sck = <&gpio0 RK_PB0_GPIO_ACTIVE_LOW>; gpio-miso = <&gpio1 RK_PB0_GPIO_ACTIVE_LOW>; gpio-mosi = <&gpio1 RK_PB1_GPIO_ACTIVE_LOW>; cs-gpios = <&gpio1 RK_PB2_GPIO_ACTIVE_LOW>; num-chipselects = <1>; pinctrl-names = "default"; pinctrl-0 = <&spi5_gpios>; status = "disabled";}; |
Then append to the pinctrl node to set pin multiplexing:
1234567 | spi5_gpios: gpios { rockchip,pins = <0 RK_PB0 0 &pcfg_pull_none>, <1 RK_PB0 0 &pcfg_pull_none>, <1 RK_PB1 0 &pcfg_pull_none>, <1 RK_PB2 0 &pcfg_pull_none>, >;}; |
Finally, modify the previously written mcp2515 node
123456789 | &spi5 { status = "okay"; mcp2515:mcp2515@0 { compatible = "rockchip,spidev"; reg = <0>; spi-max-frequency = <10000000>; status = "okay"; };} |
Test:
12 | ls /dev/spidev5.0./spidev_test -D /dev/spidev5.0 -v |
Porting the official mcp2515 driver
The Linux kernel source code already includes the MCP2515 driver by default. The specific path of the driver isdrivers/net/can/spi/mcp251x.c, so you only need tomake menuconfigselect it in the graphical configuration interface:
12345 | > Networking support > CAN bus subsystem support > CAN Device Drivers > CAN SPI interfaces <*> Microchip MCP251x and MCP25625 SPI CAN controllers |
Then add the following node to the device tree:
1234567891011121314151617181920212223 | &spi0 { status = "okay"; pinctrl-0 = <&spi0m1_cs0 &spi0m1_pins>; pinctrl-1 = <&spi0m1_cs0 &spi0m1_pins_hs>; mcp2515: mcp2515@0 { compatible = "microchip,mcp2515"; reg = <0>; spi-max-frequency = <10000000>; interrupt-parent = <&gpio0>; interrupts = <RK_PB0 IRQ_TYPE_EDGE_FALLING>; pinctrl-names = "default"; pinctrl-0 = <&mcp2515_int>; clocks = <&clk8m>; status = "okay"; }; clk8m: clk8m { compatible = "fixed-clock"; clock-frequency = <8000000>; };}; |
Add the following content under the pinctrl node
12345 | mcp2515-gpio{ mcp2515_int:mcp2515-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; };}; |
Test
12345678 | ifconfig -a# Loopback testip link set can1 downip link set can1 type can bitrate 250000ip link set can1 type can loopback onip link set up can1candump can1 -L &cansend can1 123#1122334455667788 |

