Cover image for Linux SPI

Linux SPI


Timeline

Timeline

2025-12-30

init

This article introduces the basics of the SPI bus in Linux driver development, elaborating on the full-duplex synchronous communication features of the SPI protocol, master-slave architecture design, and multi-slave device connection modes. It also explains the hardware connection principles of the four signal lines SCLK, MOSI, MISO, and CS, as well as the data communication mechanism based on serial shift registers.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Competition
5. Advanced Character Device Progression
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hot Plug
11. pinctrl Subsystem
12. gpio Subsystem
13. Input Subsystem
14. Single Bus
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network Device
23. ADC
24. IIO
25. USB
26. LCD

SPI Basics

SPI Characteristics

SPI (Serial Peripheral Interface) was originally proposed and developed by Motorola in the late 1980s as a serial communication protocol. At that time, with the advancement of microcontroller technology, an increasing number of peripheral devices needed to exchange data with microcontrollers.

However, traditional parallel bus interfaces had issues such as a large number of pins, complicated wiring, and high power consumption, making them unsuitable for the needs of embedded systems.

To address this, 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 concept of SPI is as follows:

  • Achieve full-duplex communication with the minimum number of pins

    • Simplex communication: Refers to signal transmission in only one direction, capable of either sending or receiving only

    • Half-duplex communication: Signals can be transmitted in both directions, but only sending or receiving is allowed at any one time (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 requires 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.

  • Uses synchronous communication mechanism

    • Synchronous communication: refers to the sender and receiver of data transmission using the same clock signal for coordination. Eachclock cycle sends or receives one data bit, 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, synchronizing data through a specific protocol. The specific 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 and slave devices

    • The master device is responsible forproviding the clock signal and controlling the communication process
    • The slave devicepassively responds to the master device’s operations.
  • Supports multiple slave device connections: SPI has multiple connection modes, mainly divided into normal mode and daisy-chain mode (daisy-chain mode is not commonly used),

    • Innormal 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 mode,the slave devices are connected in series, data is transmitted from the master device to the first slave device, and then from the first slave device to the next, forming a chain structure. The specific connection diagram is as follows:

    Daisy Chain Mode
    Daisy Chain Mode

SPI Hardware Connection

SPI uses 4 signal lines for communication, which areSCLKMOSIMISOandCS. Now, the specific roles and functions of each signal line are 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 needed. The 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 for the master device to transmit 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 the slave device to transmit data 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 selection signal

This is the signal line used by the master device to select the slave device for communication. When the master pulls the CS/SS line of a slave device low, it indicates that the slave device is selected for communication. The master can select different slave devices for communication 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 SCLK output of the master device to the SCLK input of the slave device

  • The MOSI line connects the MOSI output of the master device to the MOSI input of the slave device

  • The MISO line connects the MISO input of the master device to the MISO output of the slave device

  • The CS/SS line connects the chip select output of the master device to the chip select input of the slave device

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

SPI communication principle

The SPI bus has the following characteristics during data transmission:

  • 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 represents logic 0.
  • Byte transmissionAfter the transmission of one byte is completed,the transmission of the next byte can begin without an acknowledgment signal。

SPI is a single-communication protocol, meaning only one master device on the bus can initiate communication, and both the SPI master and slave devices have aserial shift register, as shown in the figure below:

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

When the SPI master wants to read/write from/to 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 sends signals on MOSI to perform a ‘write’, while simultaneously sampling MISO to perform a ‘read’

Within one SPI clock cycle, transmission and reception occur simultaneously

  • The master sends 1 bit of data via the MOSI line, and the slave reads this 1 bit of data via the same line;
  • The slave sends 1 bit of data via the MISO line, and the master reads this 1 bit of data via the same line;

When all the contents of the registers have been shifted out, it is equivalent to completing the exchange of the contents of the two registers. If the master wants to transmit data to the slave, the master only needs to ignore the data received from the slave. If the master wants to receive data from the slave, the master sends random data to the slave, and the slave ignores the data received from the master.

SPI Polarity and Phase

Before SPI communication, it is necessary to firstdetermine the default state of the clock signalandthe sampling time of the clock signal. These two parameters are determined byCPOL (Clock Polarity)andCPHA (Clock Phase).

  1. CPOL (Clock Polarity): CPOL definesthe default state of the clock signal(, i.e., the idle state.)
    • When CPOL = 0, it means the clock signal is low (0) in the idle state.
    • When CPOL = 1, it means the clock signal is high (1) in the idle state.
  2. CPHA (Clock Phase): CPHA definesthe sampling time of the data signal relative to the clock signal
    • When CPHA = 0, data is sampled on thefirst edge (rising or falling) of the clock
    • When CPHA = 1, data is sampled on thesecond edge (rising or falling) of the clock

Four combination modes:

  1. CPOL=0, CPHA=0: Clock idle low, data 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 low, data 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 high, data 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 high, data 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 normal communication is not possible.

In general, the master device configures CPOL and CPHA, and the slave device must match the master’s settings.

SPI interface in the iTOP-RK3568 processor

iTOP-RK3568 supports 4 SPI controllers

  • 1 controller supports 1 chip select output, and the other 3 controllers each support 2 chip select outputs
  • Supports master mode and slave mode, configurable via software

RK3568 Core Board Datasheet
RK3568 Core Board Datasheet

The 4 SPI interfaces here refer to hardware SPI, with dedicated hardware SPI circuits on the SOC. The introduction to hardware SPI is as follows:

Hardware SPI:

  • Implementation: The SPI communication protocol is realized through dedicated hardware circuits.
  • Advantages:
    • Low CPU usage: The SPI bus is automatically handled by the hardware circuit, requiring no direct CPU intervention.
    • High transmission rate: Can achieve communication speeds of tens of MHz or even higher.
    • Flexible configuration: Hardware SPI typically provides CPOL and CPHA configuration options for setting clock polarity and phase.
  • Disadvantages:
    • Requires dedicated hardware: Needs support from a dedicated hardware SPI interface module, resulting in relatively higher cost.
    • Fixed interface: The interface is fixed and less flexible than software SPI.
  • Scope of application: Suitable for high-speed, large-volume data transmission scenarios, such as connecting peripherals like LCD and EEPROM.

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 built-in hardware SPI interface.
  • Disadvantages:
    • High CPU usage: The CPU program is required to perform 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, typically slower than hardware SPI.
  • Scope of application: Suitable for systems without a built-in hardware SPI interface, or scenarios requiring highly customized SPI communication.

The specific usage of SPI interfaces on the RK3568 development board is shown in the following table:

SPI0

pinctrl functionNetwork LabelCorresponding GPIOFunction
SPI0_CLK_M0TP_INT_L_GPI00_B5GPIO00_B5PCIE2.0 Wake
SPI0_MISO_M0LCD1_PWREN_H_GPI00_C5GPIO00_C5Not Used
SPI0_MOSI_M0TP_RST_L_GPI00_B6GPIO00_B6MIPI Screen Touch Reset Pin
SPI0_CS0_M04G_PWREN_H_GPI00_C6GPIO00_C6Not Used
SPI0_CS1_M0LCD1_BL_PWM5GPIO00_C4MIPI Screen Enable Pin
SPI0_CLK_M1PCIE30X1_WAKEn_M1GPIO2_D3Backplane SPI
SPI0_MISO_M1PCIE20_CLKREQn_M1GPIO2_D0Backplane SPI
SPI0_MOSI_M1PCIE20_WAKEn_M1GPIO2_D1Backplane SPI
SPI0_CS0_M1PCIE30X1_CLKREQn_M1GPIO2_D2Backplane 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_C0Network Port 0
SPI1_CS1_M0CLK32K_OUT1_WIFIGPIO2_C6Unused
SPI1_CLK_M1GMAC0_TXENGPIO3_C3Network 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_C1Network Port 0 Clock
SPI2_MISO_M0GMAC0_MCLKINOUTGPIO2_C2Network Port 0 Clock
SPI2_MOSI_M0GMAC0_MDCGPIO2_C3Network Port 0
SPI2_CS0_M0GMAC0_MDIOGPIO2_C4Network 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 sets of pinctrl, but a specific hardware SPI can only be multiplexed by one set of pinctrl pins.

mcp2515

This module can not only implement the SPI to CAN function, but also the TTL to 485 function. The relevant schematic diagram is as follows:

mcp2515
mcp2515

The above schematic diagram involves two chips in total, namelyMCP2515 SPI to CAN chipandMCP2551 CAN transceiver chipHere, the MCP2515 chip is mainly introduced.

The MCP2515 is an independent 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. Fully supports CAN V2.0B: Supports transmission and reception of standard and extended data frames and remote frames.
  2. Efficient filtering function: Built-in two acceptance mask registers and six acceptance filter registers can filter out unwanted messages, reducing the processing burden on the main MCU.
  3. SPI interface: Communicates with the main controller via the SPI interface, providing efficient data transmission.
  4. Multiple operating modes: Includes normal mode, sleep mode, listen-only mode, and loopback mode to meet different application requirements.
  5. Automatic retransmission: Automatically retransmits upon transmission failure, ensuring data transmission reliability.
  6. Error detection and handling: Built-in error detection and handling mechanism ensures data transmission accuracy.

The specific connection diagram of the SPI-to-CAN module and the iTOP-RK3568 development board is shown below.

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

SPI subsystem framework

SPI subsystem framework
SPI subsystem framework

The above SPI subsystem 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 role 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, enabling upper-layer applications to conveniently interact with SPI devices.

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 an interface for upper-layer applications to access the device
    • By opening, reading/writing, and 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 via spi_device.
    • Provides a standardized interface for device access to the upper layer.

SPI adapter driver layer

The SPI adapter driver layer is an important part of the SPI subsystem, responsible for implementing the driver for the specific SPI hardware controller. The role of the SPI adapter driver is as follows:

  • Provides a standardized SPI transmission interface
    • The adapter driver layer provides standardized transmission interfaces for the SPI core layer, ensuring that different SPI controllers can uniformly use these interfaces for data transfer.
  • Implement timing control and data transmission/reception of the SPI bus protocol.
    • Responsible for implementing timing control of the SPI bus protocol, including configuration of clock polarity (CPOL) and clock phase (CPHA).
    • Manage data transmission and reception, ensuring accuracy and reliability of data transfer.
    • Automatically generate clock signals and handle 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 interactions between the master device and slave devices.
  • Handle SPI bus errors and exceptional conditions.
    • The adapter driver layer is responsible for monitoring and handling errors and exceptional conditions on the SPI bus.
    • Provide error recovery and retry mechanisms to ensure system stability and reliability.
    • Handle hardware interrupts to promptly respond to various exceptional conditions 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 two, 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 and 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 comply with 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 comply with the SPI protocol, and performs actual bus operations through the corresponding SPI adapter driver.

The specific role of the core layer is as follows:

  • The core layer is responsible for transferring data between the device driver layer and the adapter driver layer. Throughspi_writeandspi_readfunctions, 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 comply with the SPI protocol, ensuring data is correctly transmitted on the SPI bus.
  • The core layer provides standardized interfaces, allowing upper-layer device drivers to conveniently perform data transmission without concerning themselves with the specific implementation of the underlying hardware.

Writing the Generic SPI Peripheral Framework

Device Tree

Pins of iTOP-RK3568 to which the SPI-to-CAN module is to be connected

Pins of iTOP-RK3568 to which the SPI-to-CAN module is to be connected
Pins of iTOP-RK3568 to which the SPI-to-CAN module is to 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 modifications to the device tree of iTOP-RK3568 can begin.

1
2
3
4
5
6
7
8
9
10
11
12
&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 to be used. By default, the pinctrl pins used by the spi0 controller arespi0m0_cs0andspi0m0_pins, but the actual pins used are the second set of pinctrl pins.

Line 7 specifies chip select 0.

Line 9, setspi clkthe output clock frequency, here set to 10M, the maximum for RK3568 is 50M. Ifregthe attribute andspi-max-frequencyare not set, the driver will not be able to enter theprobe spiinitialization function (the match function in the driver).

regandspi-max-frequencyattribute

In rk3568.dtsi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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’scompatibleattribute to find the corresponding SPI controller driver, the specific driver file path isspi/spi-rockchip.c, the content of the driver’s probe function is as follows:

rockchip_spi_probe()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
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 if the device node is configured as SPI slave mode
slave_mode = of_property_read_bool(np, "spi-slave");
// Allocate 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 IO 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 Resource
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 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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 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 Device Resource Manager (devres) Memory for Automatic Release of 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;// Registration successful, store the pointer in the device resource manager
devres_add(dev, ptr);
} else {
devres_free(ptr);// Registration failed, release allocated resources
}

return ret;// Return registration result
}
EXPORT_SYMBOL_GPL(devm_spi_register_controller);

spi_register_controller()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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 all necessary operations are 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);

At line 132 of this function, the device tree resource registration function is calledof_register_spi_devicesRegister the device tree of SPI child nodes; the specific content of this function is as follows:

of_register_spi_devices()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 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's 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 is already marked as filled, skip it
continue;
spi = of_register_spi_device(ctlr, nc);// Register an SPI device for this node
if (IS_ERR(spi)) {// If registration fails, log a warning message and clear the filled flag of this node
dev_warn(&ctlr->dev,
"Failed to create SPI device for %pOF\n", nc);
of_node_clear_flag(nc, OF_POPULATED);
}
}
}

Line 17 iterates through each child node under the controller device tree node, usingof_register_spi_devicefunction to register this SPI child node,of_register_spi_deviceThe specific content of the function is as follows:

of_register_spi_device()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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 a 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 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 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 a 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 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 calls theof_spi_parse_dtfunction to parse SPI information in the device tree child node,of_spi_parse_dtThe specific content of the function is as follows

of_spi_parse_dt()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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 device's DUAL/QUAD mode */
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-frequencytwo attributes, it will return rc, which causes the upper-level functionof_register_spi_deviceto return an error, thus failing to successfully register the SPI device and parse the device tree node, ultimately causing the written SPI device driver to fail to match properly.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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");

Example of registering a character device

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#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 number
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 function
void mcp2515_reset(void){
int ret;
char write_buf[] = {0xc0}; // Reset command 0x11000000 i.e. 0xc0
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 function
char mcp2515_read_reg(char reg) {
char write_buf[] = {0x03, reg}; // SPI write buffer writes SPI read command 0x03
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;
}


// Callback function for opening device file
int mcp2515_open(struct inode *inode, struct file *file) {
return 0; // Return success
}

// Callback function for reading device file
ssize_t mcp2515_read(struct file *file, char __user *buf, size_t size, loff_t *offset) {
return 0; // Return success
}

// Callback function for writing device file
ssize_t mcp2515_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) {
return 0; // Return success
}

// Callback function for closing device file
int mcp2515_release(struct inode *inode, struct file *file) {
return 0; // Return success
}

// Device file operation set
struct file_operations mcp2515_fops = {
.open = mcp2515_open,
.read = mcp2515_read,
.write = mcp2515_write,
.release = mcp2515_release,
};

// MCP2515 device initialization function
int 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 read value

return 0; // Return success
}

// MCP2515 SPI device remove function
static 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 for device tree matching
static const struct of_device_id mcp2515_of_match_table[] = {
{ .compatible = "my-mcp2515" },
{}
};

// MCP2515 device ID match table for bus matching
static const struct spi_device_id mcp2515_id_table[] = {
{ "mcp2515", 0 },
{}
};

// MCP2515 SPI driver structure
static struct spi_driver spi_mcp2515 = {
.probe = mcp2515_probe, // Probe function
.remove = mcp2515_remove, // Remove function
.driver = {
.name = "mcp2515", // Driver name
.owner = THIS_MODULE, // Module
.of_match_table = mcp2515_of_match_table, // Device tree match table
},
.id_table = mcp2515_id_table, // Device ID match table
};

// Driver initialization function
static int __init mcp2515_init(void)
{
int ret;

// Register SPI driver
ret = spi_register_driver(&spi_mcp2515);
if (ret < 0) {
// Registration failed, print error message
printk("spi_register_driver error\n");
return ret;
}

return ret;
}

// Driver exit function
static 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 SPI slave devices,spi_writeThe function is defined ininclude/linux/spi/spi.hfile, as shown below

spi_write()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 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 int
spi_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 its size, then callsspi_sync_transferthe function for input transmission,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 an SPI slave device. It contains various information and configuration options related to the SPI device. The specific content of this structure is as follows:

struct spi_device

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/**
* 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 slave device,spi_readThe specific content of the function is as follows:

spi_read()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 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 int
spi_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_writefunction,spi_readThe function also performs data packaging, encapsulating the data buffer and data length len into aspi_transfertype structure,struct spi_transferis a structure describing SPI data transmission, used to configure various parameters of a single SPI data transfer. The specific content of this structure is as follows:

struct spi_transfer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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;// Data Transfer Length

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 for the transmit buffer
struct sg_table rx_sg;// Scatter-gather table for the receive buffer

unsigned cs_change:1;// Whether to change chip select state after transmission
unsigned tx_nbits:3;// Number of bits for transmission (single, dual, or quad line)
unsigned rx_nbits:3;// Number of bits for reception (single, dual, or quad line)
#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 transmissions (microseconds)
struct spi_delay delay;
struct spi_delay cs_change_delay;
struct spi_delay word_delay;// Delay between each word
u32 speed_hz;// Transmission speed (hertz)

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()

whilespi_writefunction andspi_readfunctions differ only instruct spi_transferthe difference in structure parameters, and are encapsulated asstruct spi_transferAfter that, another encapsulation is needed,spi_writethe function andspi_readthe function will ultimately callspi_sync_transferfunction, the specific content of which is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 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 int
spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers,
unsigned int num_xfers)
{
struct spi_message msg;
// Initialize SPI message with the given transfer
spi_message_init_with_transfers(&msg, xfers, num_xfers);
// Send SPI message in synchronous mode
return spi_sync(spi, &msg);
}

This function is mainly used to encapsulate SPI synchronous transfer operations, simplifying the calling process. It callsspi_message_init_with_transfersfunction to initialize the SPI transfer data, and finally callsspi_syncfunction to send SPI data in synchronous mode,spi_syncthe specific content of the function is as follows:

spi_sync()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 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 bus lock mutex of the SPI controller
mutex_lock(&spi->controller->bus_lock_mutex);

// Execute 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 function of this function is to ensure that SPI data transfer operations are performed under the protection of a mutex lock 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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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;
// Validate 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 spinlock and save interrupt flags

trace_spi_message_submit(message);// Record trace information for SPI message submission

status = __spi_queued_transfer(spi, message, false);// Perform queue transfer

spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);// Unlock the bus lock spinlock and restore interrupt flags
} else {
status = spi_async_locked(spi, message);// Asynchronous lock transfer
}

if (status == 0) {
/* Push out the messages in the calling context if we
* can.
*/
if (ctlr->transfer == spi_queued_transfer) {/* Push message in calling context if possible */
// Update statistics for synchronous immediate transfer
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 transmission message, verifying the validity of the message and device, handling the transmission, and returning the transmission 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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/**
* __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 messages are 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 if 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 transmission is successful, 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 transmission. By calling this function, the current SPI messagectlr->cur_msgis passed to this function for processing. This function is typically responsible for sending message data to the SPI device or receiving data from the device, and communicating with the hardware.

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, thereby completing the message transmission operation.

spi_write_then_read()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* 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 the 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 transmit data to the 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 is successful, copy the received data to the receive buffer

if (x[0].tx_buf == buf)// Release the lock or free the memory
mutex_unlock(&lock);
else
kfree(local_buf);

return status;// Return the transfer status
}
EXPORT_SYMBOL_GPL(spi_write_then_read);

MCP2515 driver development

The MCP2515 has five modes, which are:

  • 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 following figure:

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

1
2
3
4
5
6
7
8
9
10
struct spi_device *spi_dev; // SPI device pointer
// MCP2515 chip reset function
void mcp2515_reset(void){
int ret;
char write_buf[] = {0xc0}; // Reset command 0x11000000, i.e., 0xc0
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
#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 definition
#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 0x2c

struct mcp2515_drv_data {
dev_t dev_num; // Device Number
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 Function
void mcp2515_reset(struct spi_device *spi_dev)
{
int ret;
char write_buf[] = { 0xc0 }; // Reset Command 0x11000000 i.e. 0xc0
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 Function
char mcp2515_read_reg(struct spi_device *spi_dev, char reg)
{
char write_buf[] = { 0x03, reg }; // Write SPI Read Command 0x03 to 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 Function
void 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 commands

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 function
void 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 commands

ret = spi_write(spi_dev, write_buf, sizeof(write_buf)); // Send SPI write command
if (ret < 0) {
printk("mcp2515_change_regbit error\n");
}
}

// Callback function for opening the device file
int 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 to user buffer
ssize_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 the 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 the receive buffer to the kernel buffer
for (i = 0; i < sizeof(r_kbuf); i++) {
r_kbuf[i] = mcp2515_read_reg(spi_dev, 0x61 + i);
}

// Clear the receive buffer full flag
mcp2515_change_regbit(spi_dev, CANINTF, 0x01, 0x00);

// Copy data from the kernel buffer to the 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 function
ssize_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 transmission complete flag
mcp2515_change_regbit(spi_dev, CANINTF, 0x04, 0x00);

return size;
}

// Callback function for closing device file
int mcp2515_release(struct inode *inode, struct file *file)
{
return 0; // Return success
}

// Device file operation set
struct file_operations mcp2515_fops = {
.open = mcp2515_open,
.read = mcp2515_read,
.write = mcp2515_write,
.release = mcp2515_release,
.owner = THIS_MODULE,
};

// MCP2515 device initialization function
int 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 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 read value

return 0; // Return success

device_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 remove function
static 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 for device tree matching
static 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 for bus matching
static const struct spi_device_id mcp2515_id_table[] = { { "mcp2515", 0 }, {} };
MODULE_DEVICE_TABLE(spi, mcp2515_id_table);

// MCP2515 SPI driver structure
static struct spi_driver spi_mcp2515 = {
.probe = mcp2515_probe, // Probe function
.remove = mcp2515_remove, // Remove function
.driver = {
.name = "mcp2515", // Driver Name
.owner = THIS_MODULE, // Module
.of_match_table = mcp2515_of_match_table, // Device Tree Match Table
},
.id_table = mcp2515_id_table, // Device ID Match Table
};

// Driver Initialization Function
module_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:

1
2
3
> 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, we directly modify the previously written mcp2515 device tree node. The modified mcp2515 node is as follows:rockchip,spidev

1
2
3
4
5
6
7
8
9
10
11
12
&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 starts, if the /dev/spidev0.0 device node exists, it proves that the device tree and kernel configuration are correct.

/dev/spidev0.0Represents 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 an SPI bus, each distinguished by a chip select (CS) signal, with device numbers starting from 0.

spidev_test Tool

spidev_testIs a command-line tool for testing and debugging SPI devices, commonly used on Linux systems. It allows users to communicate directly with devices via the SPI bus, sending data and receiving responses from the device.spidev_testThe source code is located in the topeet Linux source code’skernel/tools/spiIn the directory, compilation requires cross-compilation.

1
2
3
4
5
make
CC=/home/topeet/Linux/linux_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-lin
ux-gnu/bin/aarch64-linux-gnu-gcc
LD=/home/topeet/Linux/linux_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-lin
ux-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 are working properly and to perform basic read and write operations on SPI devices.

  • Main Options and Parameters

    • -D /dev/spidevX.Y: Specifies the SPI device node to be tested.
    • -s <speed>: Sets the SPI clock frequency (in Hz), for example -s 1000000 means 1 MHz.
    • -d <delay>: Sets the delay time between data transfers (in microseconds).
    • -b <bits per word>: Sets the number of bits per data word, typically 8 or 16.
    • -H: Displays 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 using 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 will continuously send 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, primarily 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 be read from the SPI device (in bytes).
    • -b <bits per word>: Set the number of bits per data word, typically 8 or 16.
    • -d <delay>: Set the delay time between data transfers (in microseconds).
  • Example Operations

    • Send and Receive 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 to thespidev_testsource 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:

1
2
3
Device Drivers --->
[*]SPI support -->
<*> GPIO-based bitbanging SPI Master //Select

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 GPIOSimulation function
DVP_PWREN0_H_GPIO0_B0GPIO0_B0SCLK
PDM_SDI3_M0_ADCGPIO1_B0MISO
PDM_SDI2_M0_ADCGPIO1_B1MOSI
PDM_SDI1_M0_ADCGPIO1_B2CS

The steps to modify the device tree are as follows:

1
2
3
4
5
6
7
8
9
10
11
12
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:

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
9
&spi5 {
status = "okay";
mcp2515:mcp2515@0 {
compatible = "rockchip,spidev";
reg = <0>;
spi-max-frequency = <10000000>;
status = "okay";
};
}

Test:

1
2
ls /dev/spidev5.0
./spidev_test -D /dev/spidev5.0 -v

Port the official mcp2515 driver

The Linux kernel source code already includes the MCP2515 driver by default, with the specific driver path beingdrivers/net/can/spi/mcp251x.c, so you only need tomake menuconfigselect it in the graphical configuration interface:

1
2
3
4
5
> Networking support
> CAN bus subsystem support
> CAN Device Drivers
> CAN SPI interfaces
<*> Microchip MCP251x and MCP25625 SPI CAN controllers

Then add the following node in the device tree:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
&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

1
2
3
4
5
mcp2515-gpio{
mcp2515_int:mcp2515-int {
rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>;
};
};

Test

1
2
3
4
5
6
7
8
ifconfig -a
# Loopback test
ip link set can1 down
ip link set can1 type can bitrate 250000
ip link set can1 type can loopback on
ip link set up can1
candump can1 -L &
cansend can1 123#1122334455667788