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 节点下添加以下配置:

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 与内核驱动的差异,凸显了内核方案在性能、实时性和系统集成度方面的显著优势。

相关推荐
sukalot5 小时前
windows显示驱动开发-缩放桌面图像(二)
windows·驱动开发
趙小贞8 小时前
字符设备驱动开发流程与实战:以 LED 驱动为例
linux·c语言·驱动开发
DeeplyMind1 天前
AMD rocr-libhsakmt分析系列3-4:svm-reserve模式实现分析
linux·驱动开发·1024程序员节·amdgpu·kfd·rocr
sukalot2 天前
windows显示驱动开发-用于连接和配置的接口(一)
驱动开发
sukalot3 天前
windows显示驱动开发-用于连接和配置的接口(二)
windows·驱动开发
熙xi.3 天前
Linux I²C 总线驱动开发:从架构到实战的完整指南
linux·c语言·驱动开发
sukalot5 天前
windows显示驱动开发-多监视器管理器(三)
windows·驱动开发
mucheni5 天前
迅为RK3568开发板OpenHarmony系统南向驱动开发手册-UART应用开发编译源码
rk3568
王廷胡_白嫖帝6 天前
1. Linux 驱动开发前景
linux·运维·驱动开发