以前文章中有分享怎么在用户层通过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 节点下添加以下配置:
dtsi
&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>; // 片选 0
spi-max-frequency = <24000000>; // SPI 输出时钟频率
};
};
1.3 内核编译与设备树生成
执行以下命令完成内核编译与设备树生成:
shell
cd /home/kevin/Code/rk356x-linux/
./build.sh lunch
Pick 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 设备驱动已成功加载:
shell
ls /sys/bus/spi/devices/
cat /sys/bus/spi/devices/spi3.0/modalias
若终端显示设备节点信息,则表示驱动加载成功:

3 SPI发送内核源码
3.1 SPI内核源码
c
#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 1000000
struct 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 on
if(power == 0)
{
//VL
set_hig(0);
udelay(10);
//VP
set_hig(1);
udelay(5);
//S1-S4
set_hig(8);
set_hig(6);
set_hig(7);
set_hig(10);
udelay(5);
//CE
set_hig(2);
}
else
{
////D1 D3 D5
set_low(3);
set_low(4);
set_low(5);
///CE
set_low(2);
udelay(5);
//S1-S4
set_low(8);
set_low(6);
set_low(7);
set_low(10);
udelay(5);
//VP
set_low(1);
udelay(10);
//VL
set_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 high
set_hig(4);set_low(0);
///ndelay(1)正好低电平400ns
ndelay(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 index
GPIO1_A0 0
GPIO1_A1 1
GPIO1_A4 2
GPIO1_B0 3
GPIO1_B1 4
GPIO1_B2 5
GPIO3_B1 6
GPIO3_B2 7
GPIO3_B5 8
GPIO3_B6 9
GPIO3_B7 10
*/
if(index == 0)
{ //GPIO1_A0
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);
*reg = GPIO_GROUPS[1].arrPin[0].val_hig;
}
else if(index == 1)
{ //GPIO1_A1
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);
*reg = GPIO_GROUPS[1].arrPin[1].val_hig;
}
else if(index == 2)
{ //GPIO1_A4
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);
*reg = GPIO_GROUPS[1].arrPin[2].val_hig;
}
else if(index == 3)
{ //GPIO1_B0
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);
*reg = GPIO_GROUPS[1].arrPin[3].val_hig;
}
else if(index == 4)
{ //GPIO1_B1
volatile 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_B1
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);
*reg = GPIO_GROUPS[3].arrPin[0].val_hig;
}
else if(index == 7)
{ //GPIO3_B2
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);
*reg = GPIO_GROUPS[3].arrPin[1].val_hig;
}
else if(index == 8)
{ //GPIO3_B5
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);
*reg = GPIO_GROUPS[3].arrPin[2].val_hig;
}
else if(index == 9)
{ //GPIO3_B6
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);
*reg = GPIO_GROUPS[3].arrPin[3].val_hig;
}
else if(index == 10)
{ //GPIO3_B7
volatile 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 index
GPIO1_A0 0
GPIO1_A1 1
GPIO1_A4 2
GPIO1_B0 3
GPIO1_B1 4
GPIO1_B2 5
GPIO3_B1 6
GPIO3_B2 7
GPIO3_B5 8
GPIO3_B6 9
GPIO3_B7 10
*/
if(index == 0)
{ //GPIO1_A0
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);
*reg = GPIO_GROUPS[1].arrPin[0].val_low;
}
else if(index == 1)
{ //GPIO1_A1
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);
*reg = GPIO_GROUPS[1].arrPin[1].val_low;
}
else if(index == 2)
{ //GPIO1_A4
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);
*reg = GPIO_GROUPS[1].arrPin[2].val_low;
}
else if(index == 3)
{ //GPIO1_B0
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[1].va_dr);
*reg = GPIO_GROUPS[1].arrPin[3].val_low;
}
else if(index == 4)
{ //GPIO1_B1
volatile 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_B1
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);
*reg = GPIO_GROUPS[3].arrPin[0].val_low;
}
else if(index == 7)
{ //GPIO3_B2
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);
*reg = GPIO_GROUPS[3].arrPin[1].val_low;
}
else if(index == 8)
{ //GPIO3_B5
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);
*reg = GPIO_GROUPS[3].arrPin[2].val_low;
}
else if(index == 9)
{ //GPIO3_B6
volatile uint32_t *reg = (volatile uint32_t *)(GPIO_GROUPS[3].va_dr);
*reg = GPIO_GROUPS[3].arrPin[3].val_low;
}
else if(index == 10)
{ //GPIO3_B7
volatile 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_A0
GPIO_GROUPS[1].arrPin[0].pin_index = 0;
//GPIO1_A1
GPIO_GROUPS[1].arrPin[1].pin_index = 1;
//GPIO1_A4
GPIO_GROUPS[1].arrPin[2].pin_index = 4;
//GPIO1_B0
GPIO_GROUPS[1].arrPin[3].pin_index = 8;
//GPIO1_B1
GPIO_GROUPS[1].arrPin[4].pin_index = 9;
//GPIO1_B2
GPIO_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_B1
GPIO_GROUPS[3].arrPin[0].pin_index = 9;
//GPIO3_B2
GPIO_GROUPS[3].arrPin[1].pin_index = 10;
//GPIO3_B5
GPIO_GROUPS[3].arrPin[2].pin_index = 13;
//GPIO3_B6
GPIO_GROUPS[3].arrPin[3].pin_index = 14;
//GPIO3_B7
GPIO_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++)
{
//export
val = 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 value
val = 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 value
val = 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 low
for (i = 0; i < 11; i++)
{
set_low(i);
}
///S
S_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 dev
misc_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查出来的)中完全一致
text
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
makefile
KERNEL_DIR=/home/kevin/Code/rk356x-linux/kernel/
ARCH=arm64
CROSS_COMPILE=aarch64-linux-gnu-
export ARCH CROSS_COMPILE
KBUILD_CFLAGS += -O0 -Wall
obj-m := inkjet3_ctl.o
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
.PHONE:clean
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
注意:这里的/home/kevin/Code/rk356x-linux/kernel/路径改成你的kicpi内核源码路径
3.3 用户层调用代码
c
//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 端
将用户端代码编译并执行:
shell
gcc -o inkjet_test inkjet_call.c
./inkjet_test spi_test
4.2 Slave 端
4.2.1 Slave 端代码
c
#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 write
if (fd < 0){
printf("Can't open %s \n",SPI_DEV_PATH); // open i2c dev file fail
exit(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 与内核驱动的差异,凸显了内核方案在性能、实时性和系统集成度方面的显著优势。