Cover image for Linux SPI

Linux SPI

Words 13k
Views
Visitors

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 ContentsLinks
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:

Parallel communication and serial communication
Parallel communication and serial communication

  • 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
      Normal mode

    • Daisy-chain modeIn this mode,slave devices are connected in seriesData 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
    Daisy-chain mode

SPI hardware connection

SPI uses 4 signal lines for communication, namely: SCLKMOSIMISO 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.The master device is responsible for providing a stable clock signal,The frequency can be adjusted as neededThe slave device needs to use this clock signal to align and sample data.

  • 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

Connection relationship between master device and slave device
Connection relationship between master device and slave device

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 transmissionAfter 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:

Both the SPI master device and the slave device have a serial shift register
Both the SPI master device and the slave device have a serial shift register

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

  1. CPOL (Clock Polarity): CPOL definesDefault 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.
  2. CPHA (Clock Phase): CPHA definesSampling 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:

  1. CPOL=0, CPHA=0: Clock idle is low, data is sampled on the first edge (rising edge) of the clock

CPOL=0, CPHA=0
CPOL=0, CPHA=0

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

CPOL=0, CPHA=1
CPOL=0, CPHA=1

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

CPOL=1, CPHA=0
CPOL=1, CPHA=0

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

CPOL=1, CPHA=1
CPOL=1, CPHA=1

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.

RK3568 Core Board Datasheet
RK3568 Core Board Datasheet

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 functionNet labelCorresponding GPIOFunction
SPI0_CLK_M0TP_INT_L_GPI00_B5GPIO00_B5PCIE2.0 wake-up
SPI0_MISO_M0LCD1_PWREN_H_GPI00_C5GPIO00_C5Unused
SPI0_MOSI_M0TP_RST_L_GPI00_B6GPIO00_B6MIPI screen touch reset pin
SPI0_CS0_M04G_PWREN_H_GPI00_C6GPIO00_C6Unused
SPI0_CS1_M0LCD1_BL_PWM5GPIO00_C4MIPI screen enable pin
SPI0_CLK_M1PCIE30X1_WAKEn_M1GPIO2_D3Baseboard backside SPI
SPI0_MISO_M1PCIE20_CLKREQn_M1GPIO2_D0Baseboard backside SPI
SPI0_MOSI_M1PCIE20_WAKEn_M1GPIO2_D1Baseboard backside SPI
SPI0_CS0_M1PCIE30X1_CLKREQn_M1GPIO2_D2Baseboard backside SPI

SPI1

pinctrl functionNet labelCorresponding GPIOFunction
SPI1_CLK_M0GMAC0_TXENGPIO2_B5Ethernet port 0
SPI1_MISO_M0GMAC0_RXD0GPIO2_B6Ethernet port 0
SPI1_MOSI_M0GMAC0_RXD1GPIO2_B7Ethernet port 0
SPI1_CS0_M0GMAC0_RXDV_CRSGPIO2_C0Ethernet port 0
SPI1_CS1_M0CLK32K_OUT1_WIFIGPIO2_C6Unused
SPI1_CLK_M1GMAC0_TXENGPIO3_C3Ethernet port 0
SPI1_MISO_M1SPK_CTL_H_GPI03_C3GPIO2_B65G Reset
SPI1_MOSI_M1PCIE20_PERSTn_M1GPIO3_C1Unused
SPI1_CS0_M1PCIE30X1_PERSTn_M1GPIO3_A1Unused

SPI2

pinctrl functionNet labelCorresponding GPIOFunction
SPI2_CLK_M0ETH0_REFCLK0_25MGPIO2_C1Ethernet port 0 clock
SPI2_MISO_M0GMAC0_MCLKINOUTGPIO2_C2Ethernet port 0 clock
SPI2_MOSI_M0GMAC0_MDCGPIO2_C3Ethernet port 0
SPI2_CS0_M0GMAC0_MDIOGPIO2_C4Ethernet port 0
SPI2_CS1_M0GPIO2_C5GPIO2_C5PCIE power enable pin
SPI2_CLK_M1PCIE30X1_PRSNT_L_GPI03_A0GPIO3_A0PCIE2.0
SPI2_MISO_M1PCIE30X2_PRSNT_L_GPI02_D7GPIO2_D7PCIE3.0
SPI2_MOSI_M1PCIE30X2_PERSTn_M1GPIO2_D6PCIE3.0
SPI2_CS0_M1PCIE30X2_WAKEn_M1GPIO2_D5PCIE3.0
SPI2_CS1_M1PCIE30X2_CLKREQn_M1GPIO2_D4PCIE3.0

SPI3

pinctrl functionNet labelCorresponding GPIOFunction
SPI3_CLK_M0ETH1_REFCLK0_25M_M1GPIO4_B3Ethernet port 1 clock
SPI3_MISO_M0GMAC1_RXD1_M1GPIO4_B0Ethernet port 1
SPI3_MOSI_M0GPIO4_B2GPIO4_B2Unused
SPI3_CS0_M0GMAC1_TXEN_M1GPIO4_A6Ethernet port 1
SPI3_CS1_M0GMAC1_RXD0_M1GPIO4_A7Ethernet port 1
SPI3_CLK_M14G_DISABLE_GPI04_C2GPIO4_C2CAN1_RX
SPI3_MISO_M1GPIO4_C5GPIO4_C5Unused
SPI3_MOSI_M1HDMI_RX_INT_L_GPI04_C3GPIO4_C3CAN1_TX
SPI3_CS0_M1GPIO4_C6GPIO4_C6Unused
SPI3_CS1_M1HDMI_TX_CEC_M0GPIO4_D1HDMI 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:

mcp2515
mcp2515

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:

  1. Full support for CAN V2.0B: supports transmission and reception of standard and extended data frames and remote frames.
  2. 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.
  3. SPI interface: communicates with the main controller via the SPI interface, providing efficient data transfer.
  4. Multiple operating modes: including normal mode, sleep mode, listen mode, and loopback mode to meet different application requirements.
  5. Automatic retransmission: automatically retransmits on transmission failure to ensure reliable data transfer.
  6. 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.

Specific connection between the SPI-to-CAN module and the iTOP-RK3568 development board.
Specific connection between the SPI-to-CAN module and the iTOP-RK3568 development board.

SPI Subsystem Framework

SPI Subsystem Framework
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. Throughspi_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

Pins of the iTOP-RK3568 to which the SPI-to-CAN module should be connected
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>;	#address-cells = <1>;	#size-cells = <0>;	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
#include <linux/init.h>#include <linux/module.h>#include <linux/spi/spi.h>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
#include <linux/init.h>#include <linux/module.h>#include <linux/spi/spi.h>#include <linux/cdev.h>#include <linux/fs.h>#include <linux/kdev_t.h>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.)#define	SPI_CPHA	0x01			/* clock phase */#define	SPI_CPOL	0x02			/* clock polarity */#define	SPI_MODE_0	(0|0)			/* (original MicroWire) */#define	SPI_MODE_1	(0|SPI_CPHA)#define	SPI_MODE_2	(SPI_CPOL|0)#define	SPI_MODE_3	(SPI_CPOL|SPI_CPHA)#define	SPI_CS_HIGH	0x04			/* chipselect active high? */#define	SPI_LSB_FIRST	0x08			/* per-word bits-on-wire */#define	SPI_3WIRE	0x10			/* SI/SO signals shared */#define	SPI_LOOP	0x20			/* loopback mode */#define	SPI_NO_CS	0x40			/* 1 dev/bus, no chipselect */#define	SPI_READY	0x80			/* slave pulls low to pause */#define	SPI_TX_DUAL	0x100			/* transmit with 2 wires */#define	SPI_TX_QUAD	0x200			/* transmit with 4 wires */#define	SPI_RX_DUAL	0x400			/* receive with 2 wires */#define	SPI_RX_QUAD	0x800			/* receive with 4 wires */#define	SPI_CS_WORD	0x1000			/* toggle cs after each word */#define	SPI_TX_OCTAL	0x2000			/* transmit with 8 wires */#define	SPI_RX_OCTAL	0x4000			/* receive with 8 wires */#define	SPI_3WIRE_HIZ	0x8000			/* high impedance turnaround */	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)#define	SPI_NBITS_SINGLE	0x01 /* 1bit transfer */#define	SPI_NBITS_DUAL		0x02 /* 2bits transfer */#define	SPI_NBITS_QUAD		0x04 /* 4bits transfer */	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;#define SPI_TRANS_FAIL_NO_START	BIT(0)	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
Only in configuration mode,can the key registers be initialized and configured, when the MCP2515 is powered on or reset, the device automatically enters configuration mode, and the MCP2515 provides a series of SPI commands. The SPI command table is shown in the figure below:

MCP2515 instruction set
MCP2515 instruction set
By sending the above SPI commands to the MCP2515, operations such as reset, read, and write can be implemented. The command format corresponding to the reset operation is11000000. 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
#include <linux/init.h>#include <linux/module.h>#include <linux/spi/spi.h>#include <linux/cdev.h>#include <linux/fs.h>#include <linux/kdev_t.h>#include <linux/uaccess.h>#define CNF1 0x2a // Register definitions#define CNF2 0x29#define CNF3 0x28#define RXB0CTRL 0x60#define CANINTE 0x2b#define CANCTRL 0xf // CAN control register#define TXB0CTRL 0x30 //Transmit buffer control register address#define CANINTF 0x2cstruct 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
    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
    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.

  • Continuous transferspidev_test -D /dev/spidevX.Y -s 1000000 -b 8 -p 'abcdefgh'

    Continuous transfer
    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 dataspidev_fdx -D /dev/spidevX.Y -s 1000000 -w 'hello' -r 5

      This 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 10

      This 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 labelCorresponding GPIOSimulated function
DVP_PWREN0_H_GPIO0_B0GPIO0_B0SCLK
PDM_SDI3_M0_ADCGPIO1_B0MISO
PDM_SDI2_M0_ADCGPIO1_B1MOSI
PDM_SDI1_M0_ADCGPIO1_B2CS

The device tree modification steps are as follows:

123456789101112
spi5: spi@gpiol {	compatible = "spi-gpio";	#address-cells = <1>;	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-cells = <0>;		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
Loading comments…