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.
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
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
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
SPI Hardware Connection
SPI uses 4 signal lines for communication, which areSCLK、MOSI、MISOandCS. 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
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 transmission:After 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
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).
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.
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:
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=1: Clock idle low, data sampled on the second edge (falling edge) of the clock
CPOL=0, CPHA=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=1: Clock idle high, data sampled on the second edge (rising edge) of the clock
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
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 function
Network Label
Corresponding GPIO
Function
SPI0_CLK_M0
TP_INT_L_GPI00_B5
GPIO00_B5
PCIE2.0 Wake
SPI0_MISO_M0
LCD1_PWREN_H_GPI00_C5
GPIO00_C5
Not Used
SPI0_MOSI_M0
TP_RST_L_GPI00_B6
GPIO00_B6
MIPI Screen Touch Reset Pin
SPI0_CS0_M0
4G_PWREN_H_GPI00_C6
GPIO00_C6
Not Used
SPI0_CS1_M0
LCD1_BL_PWM5
GPIO00_C4
MIPI Screen Enable Pin
SPI0_CLK_M1
PCIE30X1_WAKEn_M1
GPIO2_D3
Backplane SPI
SPI0_MISO_M1
PCIE20_CLKREQn_M1
GPIO2_D0
Backplane SPI
SPI0_MOSI_M1
PCIE20_WAKEn_M1
GPIO2_D1
Backplane SPI
SPI0_CS0_M1
PCIE30X1_CLKREQn_M1
GPIO2_D2
Backplane SPI
SPI1
pinctrl function
Net Label
Corresponding GPIO
Function
SPI1_CLK_M0
GMAC0_TXEN
GPIO2_B5
Ethernet Port 0
SPI1_MISO_M0
GMAC0_RXD0
GPIO2_B6
Ethernet Port 0
SPI1_MOSI_M0
GMAC0_RXD1
GPIO2_B7
Ethernet Port 0
SPI1_CS0_M0
GMAC0_RXDV_CRS
GPIO2_C0
Network Port 0
SPI1_CS1_M0
CLK32K_OUT1_WIFI
GPIO2_C6
Unused
SPI1_CLK_M1
GMAC0_TXEN
GPIO3_C3
Network Port 0
SPI1_MISO_M1
SPK_CTL_H_GPI03_C3
GPIO2_B6
5G Reset
SPI1_MOSI_M1
PCIE20_PERSTn_M1
GPIO3_C1
Unused
SPI1_CS0_M1
PCIE30X1_PERSTn_M1
GPIO3_A1
Unused
SPI2
pinctrl function
Net Label
Corresponding GPIO
Function
SPI2_CLK_M0
ETH0_REFCLK0_25M
GPIO2_C1
Network Port 0 Clock
SPI2_MISO_M0
GMAC0_MCLKINOUT
GPIO2_C2
Network Port 0 Clock
SPI2_MOSI_M0
GMAC0_MDC
GPIO2_C3
Network Port 0
SPI2_CS0_M0
GMAC0_MDIO
GPIO2_C4
Network Port 0
SPI2_CS1_M0
GPIO2_C5
GPIO2_C5
PCIE Power Enable Pin
SPI2_CLK_M1
PCIE30X1_PRSNT_L_GPI03_A0
GPIO3_A0
PCIE2.0
SPI2_MISO_M1
PCIE30X2_PRSNT_L_GPI02_D7
GPIO2_D7
PCIE3.0
SPI2_MOSI_M1
PCIE30X2_PERSTn_M1
GPIO2_D6
PCIE3.0
SPI2_CS0_M1
PCIE30X2_WAKEn_M1
GPIO2_D5
PCIE3.0
SPI2_CS1_M1
PCIE30X2_CLKREQn_M1
GPIO2_D4
PCIE3.0
SPI3
pinctrl function
Net Label
Corresponding GPIO
Function
SPI3_CLK_M0
ETH1_REFCLK0_25M_M1
GPIO4_B3
Ethernet Port 1 Clock
SPI3_MISO_M0
GMAC1_RXD1_M1
GPIO4_B0
Ethernet Port 1
SPI3_MOSI_M0
GPIO4_B2
GPIO4_B2
Unused
SPI3_CS0_M0
GMAC1_TXEN_M1
GPIO4_A6
Ethernet Port 1
SPI3_CS1_M0
GMAC1_RXD0_M1
GPIO4_A7
Ethernet Port 1
SPI3_CLK_M1
4G_DISABLE_GPI04_C2
GPIO4_C2
CAN1_RX
SPI3_MISO_M1
GPIO4_C5
GPIO4_C5
Unused
SPI3_MOSI_M1
HDMI_RX_INT_L_GPI04_C3
GPIO4_C3
CAN1_TX
SPI3_CS0_M1
GPIO4_C6
GPIO4_C6
Unused
SPI3_CS1_M1
HDMI_TX_CEC_M0
GPIO4_D1
HDMI CEC Pin
Each SPI controller has two 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
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:
Fully supports CAN V2.0B: Supports transmission and reception of standard and extended data frames and remote frames.
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.
SPI interface: Communicates with the main controller via the SPI interface, providing efficient data transmission.
Multiple operating modes: Includes normal mode, sleep mode, listen-only mode, and loopback mode to meet different application requirements.
Automatic retransmission: Automatically retransmits upon transmission failure, ensuring data transmission reliability.
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
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
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.
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).
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:
/* 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); } elseif (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];
intspi_register_controller(struct spi_controller *ctlr) { structdevice *dev = ctlr->dev.parent; structboardinfo *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; } elseif (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"); } elseif (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);
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() - 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. */ staticvoidof_register_spi_devices(struct spi_controller *ctlr) { structspi_device *spi; structdevice_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:
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; }
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
if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) { switch (value) { case1: break; case2: spi->mode |= SPI_RX_DUAL; break; case4: spi->mode |= SPI_RX_QUAD; break; case8: 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; } return0; }
/* 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;
return0; }
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.
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 - 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. */ staticinlineint spi_write(struct spi_device *spi, constvoid *buf, size_t len) { structspi_transfert = { .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 - 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. */ structspi_device { structdevicedev;// Device structure of the generic device model structspi_controller *controller;// Pointer to the controller structspi_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 constchar *driver_override;// Driver Override int cs_gpio; /* LEGACY: chip select gpio */// Chip Select GPIO Pin structgpio_desc *cs_gpiod;/* chip select gpio desc */// Chip Select GPIO Pin structspi_delayword_delay;
/* the statistics */ structspi_statisticsstatistics;// 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 - 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. */ staticinlineint spi_read(struct spi_device *spi, void *buf, size_t len) { structspi_transfert = { .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:
structspi_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 */ constvoid *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 structsg_tabletx_sg;// Scatter-gather table for the transmit buffer structsg_tablerx_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) structspi_delaydelay; structspi_delaycs_change_delay; structspi_delayword_delay;// Delay between each word u32 speed_hz;// Transmission speed (hertz)
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:
/** * 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. */ staticinlineint spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsignedint num_xfers) { structspi_messagemsg; // 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 - 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. */ intspi_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
/* 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 - 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. */ staticvoid __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread) { structspi_transfer *xfer; structspi_message *msg; bool was_busy = false; unsignedlong flags; int ret;
/* 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; }
/* 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;
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);
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 - 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. */ intspi_write_then_read(struct spi_device *spi, constvoid *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx) { staticDEFINE_MUTEX(lock);// Define a static mutex
int status; structspi_messagemessage; structspi_transferx[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 setBy 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
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 voidmcp2515_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 voidmcp2515_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 intmcp2515_open(struct inode *inode, struct file *file) { structmcp2515_drv_data *drv_data = container_of(inode->i_cdev, struct mcp2515_drv_data, mcp2515_cdev); file->private_data = drv_data; return0; // Return success }
// Read device operation function, reads data from device to user buffer ssize_tmcp2515_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; structmcp2515_drv_data *drv_data = file->private_data; structspi_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 }
return0; // Return 0 indicates successful data read }
// 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 intmcp2515_release(struct inode *inode, struct file *file) { return0; // Return success }
// MCP2515 device initialization function intmcp2515_probe(struct spi_device *spi) { int ret, value; structmcp2515_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; }
// MCP2515 device match table for device tree matching staticconststructof_device_idmcp2515_of_match_table[] = { { .compatible = "my-mcp2515" }, {} }; MODULE_DEVICE_TABLE(of, mcp2515_of_match_table);
// MCP2515 device ID match table for bus matching staticconststructspi_device_idmcp2515_id_table[] = { { "mcp2515", 0 }, {} }; MODULE_DEVICE_TABLE(spi, mcp2515_id_table);
// MCP2515 SPI driver structure staticstructspi_driverspi_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
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.
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.
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).
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:
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
ifconfig -a # Loopback test ip linkset can1 down ip linkset can1 type can bitrate 250000 ip linkset can1 type can loopback on ip linkset up can1 candump can1 -L & cansend can1 123#1122334455667788