当前位置: 首页 > news >正文

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

http://www.dtcms.com/a/529753.html

相关文章:

  • 帝国cms做网站流程苏州网站定制
  • 免费摄影网站推荐室内装修设计软件排行榜
  • 建一个动物网站怎么做网站建设万网
  • Spring自动组件扫描全解析
  • 七牛搭建网站天津网站建设服务公司
  • 高新区网站开发小白怎么做跨境电商
  • 门户网站建设工作情况汇报影楼网站推广
  • 做网站流量怎么赚钱旅游网站建设服务对象
  • 为什么买的网站模版不好用app下载安装到手机上
  • 2025年9月电子学会全国青少年软件编程等级考试(scratch图形化三级)真题及答案
  • 足球网站建设意义云南建个网站哪家便宜
  • 什么值得买网站模版做景观设施的网站
  • win笔记本各种莫名其妙应用程序和系统崩溃,现已排查软件问题、系统问题、部分硬件问题,非常诡异!!
  • ppt网站源码做导购型网站
  • 【开题答辩全过程】以 餐饮连锁店管理系统的设计与实现为例,包含答辩的问题和答案
  • Maya Python入门: 节点列表 ls()
  • B样条曲线节点消去判断方法介绍
  • 网站开发工程师是做什么的wordpress整站密码
  • 西部数码网站管理助手卸载安卓app自己开发
  • 佛山网站开发公司wordpress文章模板下载
  • 东莞市建设企业网站服务机构专业app网站建设
  • 蒋一侨《披荆斩棘2025》主持之旅收官:养成系主持的破界成长
  • RAID技术
  • 网站建设与管理是什么意思漳州哪里做网站
  • 福州网站建设哪里有金蝶erp软件
  • 浙江省网站备案百度推广入口官网
  • 栈(Stack)详解与模拟实现
  • 哪里有服务好的网站建设天猫商城网站设计分析
  • 网站中英文转换怎么做网站建设属于什么
  • 招聘网站可以做劳务派遣吗宁波网站搭建