KickPi RK3568平台SPI内核驱动开发
以前文章中有分享怎么在用户层通过spidev进行spi通信,用户层通过 spidev 进行 SPI 通信,其优势在于开发极其便捷,无需编写内核模块即可快速验证硬件,堪称原型开发的利器;然而,其代价是性能与实时性较差,因每次通信都涉及用户态与内核态的切换开销,且难以保证精确时序。相比之下,内核层驱动开发复杂,但能提供极高的性能和稳定性,它直接在内核空间操作,无模式切换损耗,可配合DMA保证时序,并能将设备深度集成到系统框架中。因此,spidev 适用于前期验证和对性能不敏感的场景,而产品化阶段则强烈推荐采用内核驱动以实现最优的可靠性、效率与集成度。

1 设备树修改及编译
本次开发基于 KickPi RK3568 开发板进行。由于该平台默认内核配置中的设备树未启用所需的内核设备驱动,因此需手动修改设备树源文件,添加必要的节点与配置以启用该驱动。
1.1 镜像编译环境配置
关于内核源码编译环境的搭建与配置,已在先前文章中详细说明,本文不再赘述。请参考:
RK3568 KickPi OS 镜像定制:实现屏幕多自适应、GPIO 解禁与用户态 SPI 接口开启
1.2 设备树修改
进入 rk356x-linux/kernel/arch/arm64/boot/dts/rockchip/ 目录,找到并编辑 rk3568-kickpi-extend-40pin.dtsi 文件,在 &spi3 节点下添加以下配置:
&spi3 {status = "okay";pinctrl-names = "default", "high_speed";pinctrl-0 = <&spi3m1_pins>;pinctrl-1 = <&spi3m1_pins_hs>;/* 配置 SPI 设备节点 */spi_dev@0 {compatible = "rockchip,spi_test_bus0_cs0";reg = <0>; // 片选 0spi-max-frequency = <24000000>; // SPI 输出时钟频率};
};
1.3 内核编译与设备树生成
执行以下命令完成内核编译与设备树生成:
cd /home/kevin/Code/rk356x-linux/
./build.sh lunchPick a chip:1. rk3566_rk3568
2. rk3588
Which would you like? [1]: 1
Switching to chip: rk3566_rk3568
Pick a defconfig:1. rockchip_defconfig
2. rockchip_rk3562_kickpi_k3_buildroot_defconfig
3. rockchip_rk3562_kickpi_k3_debian_defconfig
4. rockchip_rk3562_kickpi_k3_ubuntu_defconfig
5. rockchip_rk3568_kickpi_k1_buildroot_defconfig
6. rockchip_rk3568_kickpi_k1_debian_defconfig
7. rockchip_rk3568_kickpi_k1_ubuntu_defconfig
8. rockchip_rk3568_kickpi_k1b_buildroot_defconfig
9. rockchip_rk3568_kickpi_k1b_debian_defconfig
10. rockchip_rk3568_kickpi_k1b_ubuntu_defconfig
Which would you like? [1]: 6./build.sh all_multi_dtb
2 镜像下载及烧录
2.1 镜像具体位置
镜像编译完成会告诉你镜像软链接具体位置,如下图所示:

双击该软链接即可跳转至镜像实际存储目录,其中 update.img 为编译生成的最新镜像文件,请将其拷贝至 Windows 环境准备烧录,如下图所示:

2.2 镜像烧录
打开RKDevTool.exe,具体烧录工具在哪下载可以去看我上篇文章,将开发板连接上usb,长按Recovery键直到出现以下界面:

点击升级固件–>固件,选择镜像后会出现以下界面:

直接点击升级,刷机完成后开发板会自动开机:

2.3 查看内核设备驱动
在开发板系统中执行以下命令,确认 SPI 设备驱动已成功加载:
ls /sys/bus/spi/devices/
cat /sys/bus/spi/devices/spi3.0/modalias
若终端显示设备节点信息,则表示驱动加载成功:

3 SPI发送内核源码
3.1 SPI内核源码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/delay.h>
//msdevice
#include <linux/miscdevice.h>#include <linux/hrtimer.h>
#include <linux/time.h>
#include <linux/gpio.h>
#include <linux/delay.h>////spi header
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/spi/spi.h>
#include <linux/device.h>
#include <linux/pinctrl/consumer.h>#define DEV_NAME "inkjet3_ctl"
static char *timer_txbuf = NULL; // 定时器使用的发送缓冲区
static int timer_data_size = 100; // 需要添加这行// 正确的定义应该是:
#define IOCTL_POWER_CTL _IO('L', 1) // 使用单个字符
#define IOCTL_START_SPI _IO('L', 2) // 使用单个字符#define GPIO1_COUNT 6
#define GPIO3_COUNT 5
//spi是gpio4,暂留GPIO4
#define GPIO4_COUNT 4#define GROUP_COUNT 5#define GPIO1_BASE (0xFE740000)
#define GPIO3_BASE (0xFE760000)
#define GPIO4_BASE (0xFE770000)///A,B-->GPIO1_DR_L和GPIO1_DDR_L///C,D-->GPIO1_DR_H和GPIO1_DDR_H//GPIO1A0,GPIO1A1,GPIO1B0,GPIO1B1-->GPIO1_DR_L和GPIO1_DDR_L
#define GPIO1_DR_L (GPIO1_BASE + 0x0000)
#define GPIO1_DDR_L (GPIO1_BASE + 0x0008)//GPIO3_A0,GPIO3_A1...-->GPIO1_DR_L和GPIO1_DDR_L
#define GPIO3_DR_L (GPIO3_BASE + 0x0000)
#define GPIO3_DDR_L (GPIO3_BASE + 0x0008)//GPIO4C3,GPIO4C5..-->GPIO2_DR_H和GPIO2_DDR_H
#define GPIO4_DR_H (GPIO4_BASE + 0x0004)
#define GPIO4_DDR_H (GPIO4_BASE + 0x000C)static dev_t devno;
struct class *inkjet_chrdev_class;typedef struct __PIN_INDEX{unsigned int pin_index;unsigned long val_hig;unsigned long val_low;
}PIN_INDEX;typedef struct __PIN_PARAMS{struct cdev dev;unsigned int __iomem *va_dr; // 数据寄存器,设置输出的电压unsigned int __iomem *va_ddr; // 数据方向寄存器,设置输入或者输出PIN_INDEX arrPin[20]; // 偏移unsigned int pin_count;}PIN_PARAMS;///GROUP1,GROUP3,GROUP4,The Number Of Array is Five for Simplifing Coding
static PIN_PARAMS GPIO_GROUPS[GROUP_COUNT];
void set_low(unsigned int index);
void set_hig(unsigned int index);
void init_pin_values(void);static unsigned char tx_buffer[100];static struct hrtimer inkjet_S_hrtimer;
static ktime_t S_ktime;static struct hrtimer inkjet_A_hrtimer;
static ktime_t A_ktime;#define MAX_SPI_DEV_NUM 8
#define SPI_MAX_SPEED_HZ 1000000struct spi_test_data {struct device *dev;struct spi_device *spi;char *rx_buf;int rx_len;char *tx_buf;int tx_len;
};static struct spi_test_data *g_spi_test_data[MAX_SPI_DEV_NUM];
static u32 bit_per_word = 8;// 初始化定时器数据缓冲区
static int init_timer_data(void)
{int i;if (timer_txbuf) {kfree(timer_txbuf);}timer_txbuf = kzalloc(timer_data_size, GFP_KERNEL);if (!timer_txbuf) {printk("Failed to allocate timer tx buffer\n");return -ENOMEM;}for (i = 0; i < 100; i++) {if(i % 5 ==0){timer_txbuf[i] = 66;}else{timer_txbuf[i] = i;}}return 0;
}int spi_write_slt(int id, const void *txbuf, size_t n)
{int ret = -1;struct spi_device *spi = NULL;struct spi_transfer t = {.tx_buf = txbuf,.len = n,.bits_per_word = bit_per_word,};struct spi_message m;if (id >= MAX_SPI_DEV_NUM)return ret;if (!g_spi_test_data[id]) {pr_err("g_spi.%d is NULL\n", id);return ret;} else {spi = g_spi_test_data[id]->spi;}spi_message_init(&m);spi_message_add_tail(&t, &m);return spi_sync(spi, &m);
}void inkjet3_power_ctl(unsigned int power)
{/////VL VP CE D1 D3 D5 S1 S2 S3 S4 10个
///VL GPIO1_A0 0
///VP GPIO1_A1 1
///CE GPIO1_A4 2
///D1 GPIO1_B0 3
///D3 GPIO1_B1 4
///D5 GPIO1_B2 5
///S1 GPIO3_B5 8
///S2 GPIO3_B1 6
///S3 GPIO3_B2 7
///S4 GPIO3_B7 10
///CS GPIO3_B6 9
//////power onif(power == 0){ //VLset_hig(0);udelay(10);//VPset_hig(1);udelay(5);//S1-S4set_hig(8);set_hig(6);set_hig(7);set_hig(10);udelay(5);//CEset_hig(2);}else{////D1 D3 D5set_low(3);set_low(4);set_low(5);///CEset_low(2);udelay(5);//S1-S4set_low(8);set_low(6);set_low(7);set_low(10);udelay(5);//VPset_low(1);udelay(10);//VLset_low(0);}}static int inkjet3_spi_probe(struct spi_device *spi)
{int ret;int id = 0;struct spi_test_data *spi_test_data = NULL;if (!spi)return -ENOMEM;if (!spi->dev.of_node)return -ENOMEM;spi_test_data = (struct spi_test_data *)kzalloc(sizeof(struct spi_test_data), GFP_KERNEL);if (!spi_test_data) {dev_err(&spi->dev, "ERR: no memory for spi_test_data\n");return -ENOMEM;}spi->bits_per_word = 8;spi_test_data->spi = spi;spi_test_data->dev = &spi->dev;ret = spi_setup(spi);if (ret < 0) {dev_err(spi_test_data->dev, "ERR: fail to setup spi\n");kfree(spi_test_data);return -1;}if (of_property_read_u32(spi->dev.of_node, "id", &id)) {dev_warn(&spi->dev, "fail to get id, default set 0\n");id = 0;}printk("=================of_property_read_u32 read id: %d\n", id);if (id >= MAX_SPI_DEV_NUM) {dev_err(&spi->dev, "id %d exceeds maximum %d\n", id, MAX_SPI_DEV_NUM);kfree(spi_test_data);return -EINVAL;}g_spi_test_data[id] = spi_test_data;printk("%s:name=%s,bus_num=%d,cs=%d,mode=%d,speed=%d\n", __func__, dev_name(&spi->dev), spi->master->bus_num, spi->chip_select, spi->mode, spi->max_speed_hz);return ret;
}static int inkjet3_spi_remove(struct spi_device *spi)
{int i;for (i = 0; i < MAX_SPI_DEV_NUM; i++) {if (g_spi_test_data[i] && g_spi_test_data[i]->spi == spi) {kfree(g_spi_test_data[i]);g_spi_test_data[i] = NULL;break;}}printk("%s\n", __func__);return 0;
}#ifdef CONFIG_OF
static const struct of_device_id inkjet3_spi_dt_match[] = {{ .compatible = "rockchip,spi_test_bus0_cs0", },//{ .compatible = "rockchip,spi_test_bus0_cs1", },{},
};
MODULE_DEVICE_TABLE(of, inkjet3_spi_dt_match);#endif /* CONFIG_OF */static struct spi_driver inkjet3_spi_driver = {.driver = {.name = "spi_test",.owner = THIS_MODULE,.of_match_table = of_match_ptr(inkjet3_spi_dt_match),},.probe = inkjet3_spi_probe,.remove = inkjet3_spi_remove,
};enum hrtimer_restart Callback_Address(struct hrtimer *timer)
{
// int res;
// if (!spi_device_ready)
// {
// printk(KERN_EMERG "SPI device not ready, skipping transfer\n");
// printk(KERN_EMERG "spi_device_ready: %d\n",spi_device_ready);
// // printk(KERN_EMERG "SPIHandle: %p\n",SPIHandle);
// // printk(KERN_EMERG "SPIHandle->spi: %p\n",SPIHandle->spi);
// goto restart_timer;
// }
// printk(KERN_EMERG "Address Function Called!!\n");
// res = spi_write_slt(0, tx_buffer, sizeof(tx_buffer));
// printk(KERN_EMERG "spi_write_slt res %d\n",res);
// A_ktime = ktime_set(5, 0); // 改为5秒间隔便于调试
// hrtimer_forward_now(timer, A_ktime);// restart_timer:A_ktime = ktime_set(5, 0); // 改为5秒间隔便于调试hrtimer_forward_now(timer, A_ktime);return HRTIMER_RESTART;
}enum hrtimer_restart Callback_Inkjet_S(struct hrtimer *timer)
{ ///S1 low,D1 highset_hig(4);set_low(0);///ndelay(1)正好低电平400nsndelay(150);set_hig(0);set_low(4);///S2//ndelay(1);set_hig(5);set_low(1);ndelay(150);set_hig(1);set_low(5);///S3//ndelay(1);set_hig(6);set_low(2);ndelay(150);set_hig(2);set_low(6);///S4//ndelay(1);set_hig(7);set_low(3);ndelay(150);set_hig(3);set_low(7);S_ktime = ktime_set(0, 1000);hrtimer_forward_now(timer, S_ktime);return HRTIMER_RESTART;
}void led_light(unsigned int epoch)
{int i = 0;for(i = 0 ;i < epoch; i++){set_hig(2);set_low(2);}}
long inkjet3_ctl(struct file *file, unsigned int cmd, unsigned long arg)
{int res; switch (cmd) {case IOCTL_POWER_CTL:printk(KERN_EMERG "IOCTL_POWER_CTL get parameter: %d\n",(unsigned int)arg);inkjet3_power_ctl((unsigned int)arg);break;case IOCTL_START_SPI:if (g_spi_test_data[0]) {res = spi_write_slt(0, timer_txbuf, timer_data_size);}else{printk(KERN_EMERG "g_spi_test_data empty!!!\n");}printk(KERN_EMERG "spi_write_slt res %d\n",res);break;default:return -ENOTTY;}return 0;
}
static struct file_operations inkjet_chrdev_fops = {.owner = THIS_MODULE,.unlocked_ioctl = inkjet3_ctl, // 设置ioctl回调
};
//注册设备信息,用于内核与用户空间简单交互
static struct miscdevice inkjet3_dev = {.minor = MISC_DYNAMIC_MINOR,.name = DEV_NAME,.fops = &inkjet_chrdev_fops,
};
void set_hig(unsigned int index)
{/*引脚对应图pin indexGPIO1_A0 0GPIO1_A1 1GPIO1_A4 2GPIO1_B0 3GPIO1_B1 4GPIO1_B2 5GPIO3_B1 6GPIO3_B2 7GPIO3_B5 8GPIO3_B6 9GPIO3_B7 10*/if(index == 0){ //GPIO1_A0volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);*reg = GPIO_GROUPS[1].arrPin[0].val_hig;}else if(index == 1){ //GPIO1_A1volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);*reg = GPIO_GROUPS[1].arrPin[1].val_hig;}else if(index == 2){ //GPIO1_A4volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);*reg = GPIO_GROUPS[1].arrPin[2].val_hig;}else if(index == 3){ //GPIO1_B0volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);*reg = GPIO_GROUPS[1].arrPin[3].val_hig;}else if(index == 4){ //GPIO1_B1volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);*reg = GPIO_GROUPS[1].arrPin[4].val_hig;}else if(index == 5){ //GPIO1_B2**volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);*reg = GPIO_GROUPS[1].arrPin[5].val_hig;}else if(index == 6){ //GPIO3_B1volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);*reg = GPIO_GROUPS[3].arrPin[0].val_hig;}else if(index == 7){ //GPIO3_B2volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);*reg = GPIO_GROUPS[3].arrPin[1].val_hig;}else if(index == 8){ //GPIO3_B5volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);*reg = GPIO_GROUPS[3].arrPin[2].val_hig;}else if(index == 9){ //GPIO3_B6volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);*reg = GPIO_GROUPS[3].arrPin[3].val_hig;}else if(index == 10){ //GPIO3_B7volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);*reg = GPIO_GROUPS[3].arrPin[4].val_hig;}else{printk(KERN_EMERG "set_hig input index error!!\n");}}
void set_low(unsigned int index)
{/*引脚对应图pin indexGPIO1_A0 0GPIO1_A1 1GPIO1_A4 2GPIO1_B0 3GPIO1_B1 4GPIO1_B2 5GPIO3_B1 6GPIO3_B2 7GPIO3_B5 8GPIO3_B6 9GPIO3_B7 10*/if(index == 0){ //GPIO1_A0volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);*reg = GPIO_GROUPS[1].arrPin[0].val_low;}else if(index == 1){ //GPIO1_A1volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);*reg = GPIO_GROUPS[1].arrPin[1].val_low;}else if(index == 2){ //GPIO1_A4volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);*reg = GPIO_GROUPS[1].arrPin[2].val_low;}else if(index == 3){ //GPIO1_B0volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);*reg = GPIO_GROUPS[1].arrPin[3].val_low;}else if(index == 4){ //GPIO1_B1volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);*reg = GPIO_GROUPS[1].arrPin[4].val_low;}else if(index == 5){ //GPIO1_B2**volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);*reg = GPIO_GROUPS[1].arrPin[5].val_low;}else if(index == 6){ //GPIO3_B1volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);*reg = GPIO_GROUPS[3].arrPin[0].val_low;}else if(index == 7){ //GPIO3_B2volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);*reg = GPIO_GROUPS[3].arrPin[1].val_low;}else if(index == 8){ //GPIO3_B5volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);*reg = GPIO_GROUPS[3].arrPin[2].val_low;}else if(index == 9){ //GPIO3_B6volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);*reg = GPIO_GROUPS[3].arrPin[3].val_low;}else if(index == 10){ //GPIO3_B7volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);*reg = GPIO_GROUPS[3].arrPin[4].val_low;}else{printk(KERN_EMERG "set_low input index error!!\n");}}
void init_pin_values(void)
{int i = 0;int j = 0;dev_t cur_dev;unsigned long val = 0;init_timer_data();////////GPIO1 GROUP SETTING//////GPIO_GROUPS[1].pin_count = GPIO1_COUNT;//GPIO1_A0GPIO_GROUPS[1].arrPin[0].pin_index = 0;//GPIO1_A1GPIO_GROUPS[1].arrPin[1].pin_index = 1;//GPIO1_A4GPIO_GROUPS[1].arrPin[2].pin_index = 4;//GPIO1_B0GPIO_GROUPS[1].arrPin[3].pin_index = 8;//GPIO1_B1GPIO_GROUPS[1].arrPin[4].pin_index = 9;//GPIO1_B2GPIO_GROUPS[1].arrPin[5].pin_index = 10;GPIO_GROUPS[1].va_dr = ioremap(GPIO1_DR_L, 4);GPIO_GROUPS[1].va_ddr = ioremap(GPIO1_DDR_L, 4);//#define GPIO3_COUNT 5////////GPIO3 GROUP SETTING//////GPIO_GROUPS[3].pin_count = GPIO3_COUNT;//GPIO3_B1GPIO_GROUPS[3].arrPin[0].pin_index = 9;//GPIO3_B2GPIO_GROUPS[3].arrPin[1].pin_index = 10;//GPIO3_B5GPIO_GROUPS[3].arrPin[2].pin_index = 13;//GPIO3_B6GPIO_GROUPS[3].arrPin[3].pin_index = 14;//GPIO3_B7GPIO_GROUPS[3].arrPin[4].pin_index = 15;GPIO_GROUPS[3].va_dr = ioremap(GPIO3_DR_L, 4);GPIO_GROUPS[3].va_ddr = ioremap(GPIO3_DDR_L, 4);alloc_chrdev_region(&devno, 0, GROUP_COUNT - 3, DEV_NAME);inkjet_chrdev_class = class_create(THIS_MODULE, DEV_NAME);for (; i < GROUP_COUNT; i++) {if(i == 0|| i == 2 || i == 4){continue;}cdev_init(&GPIO_GROUPS[i].dev, &inkjet_chrdev_fops);GPIO_GROUPS[i].dev.owner = THIS_MODULE;cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + i);cdev_add(&GPIO_GROUPS[i].dev, cur_dev, 1);device_create(inkjet_chrdev_class, NULL, cur_dev, NULL,DEV_NAME "%d", i);}// printk(KERN_EMERG "open\n");////////GPIO0 GROUP PINS EXPORT AND SAVE VALUE////////五组GPIO(GPIO0-GPIO4)for(i = 0; i < GROUP_COUNT; i++){if(i == 0|| i == 2 || i == 4){continue;}for(j = 0; j < GPIO_GROUPS[i].pin_count; j++){//exportval = ioread32(GPIO_GROUPS[i].va_ddr);val |= ((unsigned int)0x1 << (GPIO_GROUPS[i].arrPin[j].pin_index+16));val |= ((unsigned int)0X1 << (GPIO_GROUPS[i].arrPin[j].pin_index));iowrite32(val, GPIO_GROUPS[i].va_ddr);//save hig and low value//high valueval = ioread32(GPIO_GROUPS[i].va_dr);val |= ((unsigned int)0x1 << (GPIO_GROUPS[i].arrPin[j].pin_index+16));val &= ~((unsigned int)0x01 << (GPIO_GROUPS[i].arrPin[j].pin_index)); iowrite32(val, GPIO_GROUPS[i].va_dr);GPIO_GROUPS[i].arrPin[j].val_low = val;//low valueval = ioread32(GPIO_GROUPS[i].va_dr);val |= ((unsigned int)0x1 << (GPIO_GROUPS[i].arrPin[j].pin_index+16));val |= ((unsigned int)0x1 << (GPIO_GROUPS[i].arrPin[j].pin_index));iowrite32(val, GPIO_GROUPS[i].va_dr);GPIO_GROUPS[i].arrPin[j].val_hig = val;}}}static __init int inkjet3_ctl_init(void)
{int i,ret;for (i = 0; i < 100; i++) {if(i % 5 == 0){tx_buffer[i] = 66;}else{tx_buffer[i] = i;}}init_pin_values();ret = misc_register(&inkjet3_dev);if (ret) {misc_deregister(&inkjet3_dev);printk(KERN_EMERG "Failed to register misc device\n");return ret;}//注册设备,用于内核与用户空间简单交互ret = spi_register_driver(&inkjet3_spi_driver);if (ret) {spi_unregister_driver(&inkjet3_spi_driver);printk(KERN_EMERG "Failed to spi_register_driver\n");return ret;}//// set all pin lowfor (i = 0; i < 11; i++) {set_low(i);}///SS_ktime = ktime_set(0, 1000); // 1 微秒 = 1000 纳秒hrtimer_init(&inkjet_S_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);inkjet_S_hrtimer.function = &Callback_Inkjet_S;hrtimer_start(&inkjet_S_hrtimer, S_ktime, HRTIMER_MODE_REL); A_ktime = ktime_set(3, 0); // 1 微秒 = 1000 纳秒hrtimer_init(&inkjet_A_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);inkjet_A_hrtimer.function = &Callback_Address;hrtimer_start(&inkjet_A_hrtimer, A_ktime, HRTIMER_MODE_REL); printk(KERN_EMERG "Initialization Success\n");return 0;
}static __exit void inkjet3_ctl_exit(void)
{int i;dev_t cur_dev;if (timer_txbuf) {kfree(timer_txbuf);}//release access devmisc_deregister(&inkjet3_dev);spi_unregister_driver(&inkjet3_spi_driver);// 首先停止所有定时器hrtimer_cancel(&inkjet_S_hrtimer);hrtimer_cancel(&inkjet_A_hrtimer);msleep(50); // 等待50毫秒确保安全printk(KERN_EMERG "hrtimer_cancel success!!\n");for (i = 0; i < GROUP_COUNT; i++) {if(i == 0|| i == 2 || i == 4){continue;}iounmap(GPIO_GROUPS[i].va_dr); // 释放模式寄存器虚拟地址iounmap(GPIO_GROUPS[i].va_ddr); // 释放输出类型寄存器虚拟地址}for (i = 0; i < GROUP_COUNT; i++) {if(i == 0|| i == 2 || i == 4){continue;}cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + i);device_destroy(inkjet_chrdev_class, cur_dev);cdev_del(&GPIO_GROUPS[i].dev);}unregister_chrdev_region(devno, 1);class_destroy(inkjet_chrdev_class);}module_init(inkjet3_ctl_init);
module_exit(inkjet3_ctl_exit);MODULE_AUTHOR("limingzhao");
MODULE_LICENSE("GPL");
注意这里的compatible属性必须在驱动和设备树(和2.3查出来的)中完全一致
static const struct of_device_id inkjet3_spi_dt_match[] = {{ .compatible = "rockchip,spi_test_bus0_cs0", },//{ .compatible = "rockchip,spi_test_bus0_cs1", },{},
};
3.2 内核驱动Makefile
KERNEL_DIR=/home/kevin/Code/rk356x-linux/kernel/ARCH=arm64
CROSS_COMPILE=aarch64-linux-gnu-
export ARCH CROSS_COMPILEKBUILD_CFLAGS += -O0 -Wall
obj-m := inkjet3_ctl.oall:$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules.PHONE:cleanclean:$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
注意:这里的/home/kevin/Code/rk356x-linux/kernel/路径改成你的kicpi内核源码路径
3.3 用户层调用代码
//inkjet_call.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>// 与内核驱动中定义的IOCTL命令保持一致
#define IOCTL_POWER_CTL _IO('L', 1)
#define IOCTL_START_SPI _IO('L', 2)int main(int argc, char *argv[])
{int fd;int power_state;int ret;// 打开设备文件fd = open("/dev/inkjet3_ctl", O_RDWR);if (fd < 0) {perror("Failed to open device");return -1;}printf("inkjet3_ctl device opened successfully\n");if (argc < 2) {printf("Usage: %s <command>\n", argv[0]);printf("Commands:\n");printf(" power_on - Turn on power (0)\n");printf(" power_off - Turn off power (1)\n");printf(" spi_test - Start SPI transfer\n");close(fd);return 0;}// 根据命令行参数执行相应操作if (strcmp(argv[1], "power_on") == 0) {power_state = 0;ret = ioctl(fd, IOCTL_POWER_CTL, power_state);if (ret == 0) {printf("Power turned ON successfully\n");} else {printf("Failed to turn power ON, ret=%d\n", ret);}}else if (strcmp(argv[1], "power_off") == 0) {power_state = 1;ret = ioctl(fd, IOCTL_POWER_CTL, power_state);if (ret == 0) {printf("Power turned OFF successfully\n");} else {printf("Failed to turn power OFF, ret=%d\n", ret);}}else if (strcmp(argv[1], "spi_test") == 0) {ret = ioctl(fd, IOCTL_START_SPI, 0);if (ret == 0) {printf("SPI transfer started successfully\n");} else {printf("SPI transfer failed, ret=%d\n", ret);}}else {printf("Unknown command: %s\n", argv[1]);printf("Available commands: power_on, power_off, spi_test\n");}close(fd);return 0;
}
4 实验
4.1 Master 端
将用户端代码编译并执行:
gcc -o inkjet_test inkjet_call.c
./inkjet_test spi_test
4.2 Slave 端
4.2.1 Slave 端代码
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <linux/spi/spidev.h>#define SPI_DEV_PATH "/dev/spidev3.0"/*SPI 接收 、发送 缓冲区*/
unsigned char rx_buffer[100];int fd; // SPI 控制引脚的设备文件描述符
static unsigned mode = SPI_MODE_0; //用于保存 SPI 工作模式
static uint8_t bits = 8; // 接收、发送数据位数
static uint32_t speed = 1000000; // 发送速度
static uint16_t delay; //保存延时时间void transfer(int fd, uint8_t *tx, uint8_t *rx, size_t len)
{int ret;struct spi_ioc_transfer tr = {.tx_buf = 0,.rx_buf = (unsigned long)rx,.len = len,.delay_usecs = delay,.speed_hz = speed,.bits_per_word = bits,};ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);if (ret < 1)printf("can't send spi message\n");
}void spi_init(int fd)
{int ret = 0;// 设置 SPI 工作模式(写入)ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);if (ret == -1)printf("can't set spi mode\n");// 设置数据位数(写入)ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);if (ret == -1)printf("can't set bits per word\n");// 设置SPI工作频率(写入)ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);if (ret == -1)printf("can't set max speed hz\n");// 验证设置unsigned read_mode;uint8_t read_bits;uint32_t read_speed;ioctl(fd, SPI_IOC_RD_MODE32, &read_mode);ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &read_bits);ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &read_speed);printf("spi mode: 0x%x (set: 0x%x)\n", read_mode, mode);printf("bits per word: %d (set: %d)\n", read_bits, bits);printf("max speed: %d Hz (set: %d Hz)\n", read_speed, speed);
}int main(int argc, char *argv[])
{/*打开 SPI 设备*/fd = open(SPI_DEV_PATH, O_RDWR); // open file and enable read and writeif (fd < 0){printf("Can't open %s \n",SPI_DEV_PATH); // open i2c dev file failexit(1);}/*初始化SPI */spi_init(fd);//for(int i=0;i<10000;i++)int recvCount =0; while(1){/*执行发送*/transfer(fd, NULL, rx_buffer, sizeof(rx_buffer));for (int j = 0; j < 100; j++) {if(rx_buffer[j] == 0 || rx_buffer[j] == 255 || rx_buffer[j] == 127){printf("rx_buffer -- \r");fflush(stdout);//recvCount =0; }else{recvCount++;//printf("Received: %d\n", byte);printf("rx_buffer[%d]:%d , recvCount: %d\n", j,(int)rx_buffer[j],recvCount);}//}}close(fd);return 0;
}
4.2.2 效果

总结
本文完整阐述了在 RK3568 平台上开发内核级 SPI 设备驱动的全过程,涵盖了从设备树配置、内核编译、驱动开发到用户层测试的完整技术链路。通过对比用户态 spidev 与内核驱动的差异,凸显了内核方案在性能、实时性和系统集成度方面的显著优势。
