本文目录
- 一、 SPI驱动框架图
- 二、编写SPI驱动device框架
- 三、编写SPI驱动driver框架
- 四、实验一编写mcp2515驱动
- 1. 注册字符设备或杂项设备框架
- 2. SPI写数据
- 3. SPI读寄存器数据
- 4. MCP2515相关配置
对于SPI基础知识这里不做过多讲解,详情查看:SPI基础知识实践讲解-STM32版。
一、 SPI驱动框架图
本框图中spi核心层和spi适配器驱动层不需要我们去关心,如果未来要去原厂工作的话,可以深入了解其工作原理和内容,这里我们不做过多介绍。
二、编写SPI驱动device框架
- 查看开发板中可用的SPI引脚。
- 修改设备树文件。
(1)修改spi控制器源码设备树文件。
将控制器的pinctrl-0复用引脚的spi0m0修改为我们所使用的spi0m1。由于原厂工程师已经写完spi控制器的引脚复用功能,所以我们只需要修改即可。
spi0: spi@fe610000 {
compatible = "rockchip,rk3066-spi";
reg = <0x0 0xfe610000 0x0 0x1000>; // SPI 控制器寄存器基地址
interrupts = <GIC SPI 103 IRQ TYPE LEVEL HIGH>; // 中断配置
#address-cells = <1>; // 地址单元数量
#size-cells = <0>; // 大小单元数量
clocks = <&cru CLK_SPI>, <&cru PCLK_SPIO>; // SPI 和 APB 时钟
clock-names = "spiclk", "apb pclk"; // 时钟名称
dmas = <&dmac0 20>, <&dmac0 21>; // DMA 通道配置(TX, RX)
dma-names = "tx", "rx"; // DMA 通道名称
pinctrl-names = "default", "high-speed"; // 引脚控制模式
// pinctrl-0 = <&spi0m0_cs0 &spi0m0_csl &spi0m0_pins>; // 默认引脚控制配置
pinctrl-0 = <&spi0m1_cs0 &spi0m1_pins>; // 默认引脚控制配置
// pinctrl-1 = <&spi0m0_cs0 &spi0m0_csl &spi0m0_pins_high_speed>; // 高速模式引脚配置
pinctrl-1 = <&spi0m1_cs0 &spi0m1_pins_high_speed>; // 高速模式引脚配置
status = "disabled"; // 当前 SPI 控制器状态,禁用状态
};
(2)添加引用spi控制器
在开发板设备树根节点下添加以下节点。如果设备树中没有reg 和 spi-max-freguengy这俩个属性就会返回错误。返回错误设备注册不成功。从而就不会和驱动匹配上(具体分析spi控制器的注册流程)。以下的SPI工作模式以及传输模式等都需要根据具体的SPI外设来设置!!
&spi0 {
status ="okay";
mcp2515:mcp2515@0{
status ="okay";
compatible="my-mcp2515";
reg = <0>; /* 1. 选择片选0 */
spi-max-frequency = <24000000>; /* 2. 设置SPI时钟最大频率为24MHz,不要超过50M */
//3. 设置工作模式,如果不写,则默认为0,0
spi-cpha = <1>; // 设置时钟相位
spi-cpol = <0>; // 设置时钟极性
//4. 设置传输模式,高位先传还是低位先传。默认为高位先传。
//spi-lsb-first = <1>; // 设置为 LSB 优先
//5. 选择片选信号为高电平/低电平选中。默认为低电平选中。
// spi-cs-high = <1>; // 设置片选信号为高电平有效
}
}
reg
属性用于设置 SPI 设备的片选引脚,而 spi-max-frequency
属性则用于设置 SPI 总线的最大时钟频率。通常,SPI 设备的片选和时钟频率都需要在设备树中进行配置,以便正确地初始化 SPI 设备。
三、编写SPI驱动driver框架
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/kernel.h>
static int mcp2515_probe(struct spi_device *spi)
{
return 0;
}
static int mcp2515_remove(struct spi_device *spi)
{
return 0;
}
static const struct of_device_id mcp2515_of_match[] = {
{ .compatible = "my-mcp2515", },
{ }
};
MODULE_DEVICE_TABLE(of, mcp2515_of_match);
MODULE_DEVICE_TABLE(spi, mcp2515_id_table);
static struct spi_driver mcp2515_driver = {
.driver = {
.name = "mcp2515",
.owner = THIS_MODULE,
.of_match_table = mcp2515_of_match,
},
.probe = mcp2515_probe,
.remove = mcp2515_remove,
};
static int __init mcp2515_init(void)
{
return spi_register_driver(&mcp2515_driver);
}
static void __exit mcp2515_exit(void)
{
spi_unregister_driver(&mcp2515_driver);
}
module_init(mcp2515_init);
module_exit(mcp2515_exit);
MODULE_LICENSE("GPL");
四、实验一编写mcp2515驱动
MCP2515 是由 Microchip 提供的一款独立的 CAN (Controller Area Network) 控制器,它通过 SPI 接口与主机通信。即SPI转CAN的一个模块。
1. 注册字符设备或杂项设备框架
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
static ssize_t myread(struct file *fp, char __user *ubuf, size_t size, loff_t *loft)
{
return 0;
}
static int my_open(struct inode *node, struct file *fp)
{
return 0;
}
ssize_t my_write(struct file *fp, const char __user *ubuf, size_t size, loff_t *loft)
{
// 若不需要实际的写操作,可以直接返回0或-EINVAL
return 0;
}
static const struct file_operations myfops = {
.read = myread,
.open = my_open,
.write = my_write,
};
static struct miscdevice mcp2515_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mcp2515",
.fops = &myfops,
};
static int mcp2515_probe(struct spi_device *spi)
{
int ret;
ret = misc_register(&mcp2515_misc);
if (ret) {
pr_err("Failed to register misc device\n");
return ret;
}
return 0;
}
static int mcp2515_remove(struct spi_device *spi)
{
pr_info("MCP2515 SPI device removed\n");
misc_deregister(&mcp2515_misc);
return 0;
}
static const struct of_device_id mcp2515_of_match[] = {
{ .compatible = "my-mcp2515", },
{ }
};
static struct spi_driver mcp2515_driver = {
.driver = {
.name = "mcp2515", //不会与之匹配
.owner = THIS_MODULE,
.of_match_table = mcp2515_of_match,
},
.probe = mcp2515_probe,
.remove = mcp2515_remove,
};
static int __init mcp2515_init(void)
{
pr_info("MCP2515 driver loading\n");
return spi_register_driver(&mcp2515_driver);
}
static void __exit mcp2515_exit(void)
{
pr_info("MCP2515 driver unloading\n");
spi_unregister_driver(&mcp2515_driver);
}
module_init(mcp2515_init);
module_exit(mcp2515_exit);
MODULE_LICENSE("GPL");
2. SPI写数据
成功时:返回发送的字节数,通常是 len。
失败时:返回负的错误代码,通常为 -EINVAL(无效参数)或 -EIO(输入/输出错误)等。
int spi_write(struct spi_device *spi, const void *buf, unsigned len);
/*
spi:指向目标 SPI 设备的指针。这是你要向其发送数据的 SPI 设备。
buf:指向包含要发送的数据的缓冲区的指针。这些数据将被通过 SPI 总线发送。
len:要发送的数据的字节数。表示从 buf 中发送的字节数。
*/
●示例: 我们通过mcp2515手册可知,对其进行写操作需要写入0x02。使用spi_write
来进行写操作。
#include <linux/spi/spi.h>
struct spi_device *spi;
void mcp2515_write_reg(char reg, char value)
{
int ret;
char write_buf[] = {0x02, reg, value}; // MCP2515 写寄存器的指令
// 向 MCP2515 发送数据
ret = spi_write(spi, write_buf, sizeof(write_buf));
if (ret < 0) {
printk(KERN_ERR "MCP2515 write reg failed: %d\n", ret);
}
}
3. SPI读寄存器数据
这里有以下两个API函数。
(1)spi_read
(直接读)
成功时:返回接收的字节数(通常是 len)。失败时:返回负的错误代码。
int spi_read(struct spi_device *spi, void *buf, unsigned len);
/*
spi:指向 SPI 设备的指针。
buf:指向存放接收数据的缓冲区的指针。接收到的数据将存放在这个缓冲区中。
len:要接收的数据的字节数。
*/
(2)spi_write_then_read
(先写后读)
这个函数用于 SPI 设备的 “write then read” 操作,即先发送数据,然后读取数据。成功时:返回传输的字节数,通常等于 n_tx 或 n_rx,这取决于设备的特性。失败时:返回负的错误代码(如 -EINVAL 或 -EIO 等)。
int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx);
/*
spi:指向 SPI 设备的指针,表示我们将要操作的 SPI 设备。
txbuf:指向要发送的缓冲区的指针。它包含我们要发送的数据。
n_tx:要发送的字节数。
rxbuf:指向接收数据的缓冲区的指针。读取到的数据会存放在这个缓冲区中。
n_rx:要接收的字节数。
*/
●示例: mcp2515读寄存器的值,我们查看其手册发现,要想读寄存器的值需要先写入0x03命令,然后发送要读取寄存器的地址,再进行读取值。所以我们需要先写后读。
#include <linux/spi/spi.h>
struct spi_device *spi;
char mcp2515_read_reg(char reg)
{
int ret;
u8 write_buf[] = { 0x03, reg }; // 0x03 是读取寄存器命令,具体命令值请参考 MCP2515 数据手册
u8 read_buf; // 用于存储读取到的数据
// SPI write then read 操作
ret = spi_write_then_read(spi, write_buf, sizeof(write_buf), &read_buf, sizeof(read_buf));
if (ret < 0) {
printk(KERN_ERR "SPI write then read error: %d\n", ret);
return ret; // 返回错误代码
}
printk(KERN_INFO "Read value from MCP2515 register 0x%02X: 0x%02X\n", reg, read_buf);
return read_buf; // 返回读取到的寄存器值
}
4. MCP2515相关配置
(1)复位操作(进入配置模式):通过查看器件手册来获取复位命令,再SPI来对其进行写入。
struct spi_device *spi;
static int mcp2515_reset()
{
int ret;
u8 write_buf[] = { 0xc0 }; // 假设 0xc0 是复位命令,具体命令根据 datasheet 确定
// 向 MCP2515 设备写入复位命令
ret = spi_write(spi, write_buf, sizeof(write_buf));
if (ret < 0) {
printk(KERN_ERR "SPI write error: %d\n", ret);
return ret;
}
printk(KERN_INFO "MCP2515 reset command sent successfully.\n");
return 0;
}