12.5在Linux中编写队列模式的SPI控制器驱动

这篇具有很好参考价值的文章主要介绍了12.5在Linux中编写队列模式的SPI控制器驱动。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在Linux内核中更加推荐使用队列模式的SPI控制器驱动,而且队列模式的SPI控制器驱动也更加简单,只需要在驱动中实现单个spi_transfer的传输即可,将spi_message拆解为spi_transfer、片选GPIO控制、统计信息更新等均由SPI核心去完成。

编写驱动程序步骤

  1. 实现设置SPI总线的函数setup,用于设置SPI总线,若片选采用GPIO编号模式还需要在这里将GPIO设置为输出
  2. 实现SPI总线数据传输的函数transfer_one,用于传输SPI的数据
  3. 通过spi_alloc_master分配一个struct spi_master
  4. 初始化struct spi_master,主要包含设备树节点、支持的模式、支持的最大频率和最小频率、片选引脚是GPIO编号模式还是描述符模式、setup函数(用于设置SPI总线)、transfer_one函数(用于传输spi_transfer,若提供了transfer_one,且未提供transfer函数则是队列模式的SPI控制器驱动)
  5. 若片选采用GPIO编号模式还需要对片选引脚进行request操作,若片选采用GPIO描述符模式则无该步骤
  6. 通过spi_register_master注册SPI控制器驱动
  7. 设备或驱动卸载时spi_unregister_master注销SPI控制器

编写驱动程

这里编写一个虚拟的SPI控制器驱动,通过printk来输出SPI控制器的工作状态,同时它提供软件进行回环(将tx_buf拷贝到rx_buf中)。

设备树编写

在顶层设备树根节点中加入如下节点:

	virtual_spi_master {
			compatible = "atk,virtual_spi_master";
			status = "okay";
			//片选列表,一个spi_master至少有一个片选
			cs-gpios = <&gpioh 6 GPIO_ACTIVE_LOW>;
			//片选数量
			num-chipselects = <1>;
			//reg中地址字段的字数,必须为1
			#address-cells = <1>;
			//reg中地址空间大小的字数,必须为0
			#size-cells = <0>;

			//一个spidev的设备节点,以便在应用层通过spidev来测试SPI控制器驱动
			virtual_spi_dev: virtual_spi_dev@0 {
					compatible = "rohm,dh2228fv";
					reg = <0>;
					spi-max-frequency = <100000>;
			};
	};

用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树,用新的.dtb文件启动系统

驱动代码编写

完整的驱动代码如下所示:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/spi/spi.h>
#include <linux/of_gpio.h>

static int spi_virtual_setup(struct spi_device *spi_dev)
{
	if(!gpio_is_valid(spi_dev->cs_gpio))
	{
		printk("%d is not a valid gpio\n", spi_dev->cs_gpio);
		return -EINVAL;
	}

	//若采用GPIO编号模式还需要在驱动中将gpio设置为输出
	return gpio_direction_output(spi_dev->cs_gpio, !(spi_dev->mode & SPI_CS_HIGH));
}

static int spi_virtual_transfer_one(struct spi_master *master, struct spi_device *spi, struct spi_transfer *transfer)
{
	int status;

	//模拟硬件传输数据
	if((transfer->tx_buf || transfer->rx_buf) && (transfer->len > 0))
	{
		if(transfer->rx_buf && transfer->tx_buf)
			memcpy(transfer->rx_buf, transfer->tx_buf, transfer->len);
		else if(transfer->rx_buf)
			memset(transfer->rx_buf, 0xAA, transfer->len);
		printk("transfer one\n");
		status = 0;
	}
	else if((transfer->tx_buf || transfer->rx_buf) && (transfer->len <= 0))
	{
		printk("transfer invalid\n");
		status = -EREMOTEIO;
	}
	else
		status = 0;

	//传输完成需要调用此函数(如果使用中断应该在传输完成中断中调用)
	spi_finalize_current_transfer(master);

	return status;
}

static int spi_virtual_probe(struct platform_device *pdev)
{
	int result;
	int i, num_cs, cs_gpio;
	struct spi_master *virtual_master;

	printk("%s\r\n", __FUNCTION__);

	//分配spi_master
	virtual_master = spi_alloc_master(&pdev->dev, 0);
	if(!virtual_master)
	{
		printk("alloc spi_master fail\n");
		return -ENOMEM;
	}

	//设置平台设备的驱动私有数据
	pdev->dev.driver_data = (void*)virtual_master;

	//初始化spi_master
	virtual_master->use_gpio_descriptors = 0;
	virtual_master->setup = spi_virtual_setup;
	virtual_master->transfer_one = spi_virtual_transfer_one;
	virtual_master->dev.of_node = pdev->dev.of_node;
	virtual_master->bus_num = pdev->id;
	virtual_master->max_speed_hz = 1000000000;
	virtual_master->min_speed_hz = 1000;
	virtual_master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH | SPI_LSB_FIRST | SPI_3WIRE;

	//片选引脚采用GPIO编号模式时,SPI驱动框架仅仅是从设备树中获取片选引脚编号并记录,未进行request引脚
	num_cs = of_gpio_named_count(pdev->dev.of_node, "cs-gpios");
	for (i = 0; i < num_cs; i++)
	{
		cs_gpio = of_get_named_gpio(pdev->dev.of_node, "cs-gpios", i);
		if (cs_gpio == -EPROBE_DEFER)
		{
			/* 释放前面分配的 spi_master,它通过于 spi_master 绑定的 dev 来实现
			* 其调用流程如下:
			* 	spi_master_put
			* 		spi_controller_put
			* 			put_device
			* 				kobject_put
			* 					kref_put
			* 						kobject_release
			* 							kobject_cleanup
			* 								t->release,这里应该是device_initialize为其注册的device_ktype中的device_release
			* 									dev->class->dev_release,这里应该是分配spi_master时为dev.class绑定的spi_master_class中的spi_controller_release
			*/
			spi_master_put(virtual_master);
			return -EPROBE_DEFER;
		}

		if(gpio_is_valid(cs_gpio))
		{
			result = devm_gpio_request(&pdev->dev, cs_gpio, "virtual_spi_cs");
			if(result < 0)
			{
				spi_master_put(virtual_master);
				printk("can't get CS gpio %i\n", cs_gpio);
				return result;
			}
		}
	}

	//注册 spi_master
	result = spi_register_master(virtual_master);
	if (result < 0)
	{
		printk("register spi_master fail\n");
		spi_master_put(virtual_master);
		return result;
	}

	return 0;
}

static int spi_virtual_remove(struct platform_device *pdev)
{
	struct spi_master *virtual_master;

	printk("%s\r\n", __FUNCTION__);

	//提取平台设备的驱动私有数据
	virtual_master = (struct spi_master*)pdev->dev.driver_data;

	//注销spi_master,在注销过程中会执行put_device操作,所以无需再次执行spi_master_put
	spi_unregister_master(virtual_master);

	return 0;
}

static const struct of_device_id spi_virtual_of_match[] = {
	{.compatible = "atk,virtual_spi_master"},
	{ /* Sentinel */ }
};
static struct platform_driver spi_virtual_driver = {
	.probe = spi_virtual_probe,
	.remove = spi_virtual_remove,
	.driver = {
		.name = "virtual_spi",
		.of_match_table = spi_virtual_of_match,
	},
};

static int virtual_master_init(void)
{
	printk("%s\r\n", __FUNCTION__);

	return platform_driver_register(&spi_virtual_driver);
}

static void virtual_master_exit(void)
{
	printk("%s\r\n", __FUNCTION__);

	platform_driver_unregister(&spi_virtual_driver);
}

module_init(virtual_master_init);
module_exit(virtual_master_exit);

MODULE_DESCRIPTION("virtual SPI bus driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");

驱动测试程序编写

驱动测试程序基于spidev进行编写,它通过ioctl控制SPI总线进行数据收发,完整的代码如下所示:文章来源地址https://www.toymoban.com/news/detail-809003.html

/* 参考: tools\spi\spidev_fdx.c */
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <errno.h>

/* dac_test /dev/spidevB.D <val> */

int main(int argc, char **argv)
{
	int fd;
	int status;
	struct spi_ioc_transfer xfer[1];
	unsigned char tx_buf[1];
	unsigned char rx_buf[1];
	
	if(argc != 3)
	{
		printf("Usage: %s /dev/spidevB.D <val>\n", argv[0]);
		return 0;
	}

	//打开spidev设备
	fd = open(argv[1], O_RDWR);
	if (fd < 0) {
		printf("can not open %s\n", argv[1]);
		return 1;
	}

	//通过ioctl控制SPI总线发送并接收一个字节的数据
	tx_buf[0] = (unsigned char)strtoul(argv[2], NULL, 0);
	rx_buf[0] = 0;
	memset(xfer, 0, sizeof xfer);
	xfer[0].tx_buf = (unsigned long)tx_buf;
	xfer[0].rx_buf = (unsigned long)rx_buf;
	xfer[0].len = 2;
	status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
	if(status < 0)
	{
		printf("SPI_IOC_MESSAGE %d\n", errno);
		return -1;
	}

	//打印接收到的数据
	printf("Pre val = %d\n", rx_buf[0]);

	return 0;
}

上机测试

  1. 修改设备树,增加虚拟SPI控制器的设备树节点,并在此节点中添加一个spidev的子节点,然后编译设备树,用新的设备树启动设备
  2. 从这里下载代码,使用make进行编译,然后使用make copy拷贝到目标板NFS跟文件系统的root目录中(执行make copy时需要确保akefile中NFS根文件系统的路径正确)
  3. 在目标板中执行insmod spi_master.ko加载虚拟SPI控制器驱动
    12.5在Linux中编写队列模式的SPI控制器驱动,linux驱动开发,linux,arm开发,嵌入式硬件,驱动开发
  4. 执行命令./spi_test.out /dev/spidev1.0 12通过SPI总线发送1byte数据,同时将收到的数据打印出来(驱动中默认将发送的数据赋给接收
    12.5在Linux中编写队列模式的SPI控制器驱动,linux驱动开发,linux,arm开发,嵌入式硬件,驱动开发

到了这里,关于12.5在Linux中编写队列模式的SPI控制器驱动的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【设计模式】前端控制器模式

    前端控制器模式(Front Controller Pattern)是用来提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理。该处理程序可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。以下是这种设计模式的实体。 前端控制器(Front Controller)  

    2024年02月13日
    浏览(49)
  • 森泰克sumtak控制器维修伺服驱动器维修SQ-12

    日本森泰克sumtak控制器维修全系列型号。 控制器常见维修故障:短路,模块损坏,带不动负载,主轴准备未绪,驱动器未使能,编码器故障,主轴驱动模块故障,输出电压低,红色灯亮,无显示,缺相,输出控制点坏,使能不正常,报故障,不能启动、过流、过压、欠压、过

    2024年02月09日
    浏览(38)
  • k8s控制器之job--第二弹编写Job的定义

    与所有的 Kubernetes 对象一样,Job 对象的 YAML 文件中,都需要包括如下三个字段: .apiVersion .kind .metadata Job 对象的 YAML 文件,还需要一个 .spec 字段。 .spec.template 是必填字段: 用于定义 pod template 与 Pod 有相同的字段内容,但由于是内嵌元素,pod template 不包括阿 apiVersion 字段和

    2024年02月10日
    浏览(38)
  • 第12关 精通K8s下的Ingress-Nginx控制器:生产环境实战配置指南

    ------ 课程视频同步分享在今日头条和B站 大家好,我是博哥爱运维,这节课带来k8s的流量入口ingress,作为业务对外服务的公网入口,它的重要性不言而喻,大家一定要仔细阅读,跟着博哥的教程一步步实操去理解。 Ingress基本概念 在Kubernetes集群中,Ingress作为集群内服务对外

    2024年02月03日
    浏览(48)
  • thinkphp8.0多应用模式下提示控制器不存在

    thinkphp 8.0 开启多应用模式 1、按照官方文档说明 ,已经安装了 think-multi-app composer require topthink/think-multi-app 2、控制器的命名空间也没写错。 3、访问路径与目录名、控制器、方法名一样,访问地址是没错的。 4、网上有说,在配置文件config/app.php中,将 ‘auto_multi_app’ = flase

    2024年02月14日
    浏览(48)
  • k8s控制器之job--第六弹Job的模式

    Kubernetes Job 对象可以用来支持 Pod 的并发执行,但是: Job 对象并非设计为支持需要紧密相互通信的Pod的并发执行,例如科学计算 Job 对象支持并发处理一系列相互独立但是又相互关联的工作任务,例如: 发送邮件 渲染页面 转码文件 扫描 NoSQL 数据库中的主键 其他 在一个复杂

    2024年02月10日
    浏览(45)
  • Linux6.39 Kubernetes Pod控制器

    第三章 LINUX Kubernetes Pod控制器 一、Pod控制器及其功用 Pod控制器,又称之为工作负载(workload),是用于实现管理pod的中间层,确保pod资源符合预期的状态,pod的资源出现故障时,会尝试进行重启,当根据重启策略无效,则会重新新建pod的资源 二.pod控制器有多种类型 1.Replic

    2024年02月12日
    浏览(43)
  • linux高级---k8s中的五种控制器

    Kubernetes中内建了很多controller(控制器),这些相当于一个状态机,用来控制Pod的具体状态和行为 总体来说,K8S有五种控制器,分别对应处理无状态应用、有状态应用、守护型应用和批处理应用 无状态服务的特点: 有状态服务的特点: Deployment主要功能有下面几个: 支持R

    2024年02月06日
    浏览(75)
  • Linux 中断子系统中GIC 中断控制器基本分析

    GIC 是 ARM 公司给 Cortex-A/R 内核提供的一个中断控制器,类似 Cortex-M 内核(STM32)中的 NVIC。 GIC:Generic Interrupt Controller,通用中断控制器。 NVIC:Nested Vectored Interrupt Controller,嵌套中断向量控制器。 目前 GIC 有 4 个版本:V1~V4,V1 是最老的版本,已经被废弃了。V2~V4 目前正在大

    2024年02月07日
    浏览(49)
  • Linux DMA子系统(2):DMA控制器驱动(provider)

    目录 1. 前言 2. 重要的结构体 2.1 struct dma_device 2.2 struct dma_chan 2.3 struct virt_dma_chan 3. 重要的API 3.1 注册及注销API 3.2 cookie相关API 4. DMA控制器驱动的编写步骤 5. 参考文章 本文将从DMA控制器驱动(provider)的角度来介绍DMA Engine,包括重要的结构体和API接口。 DMA控制器驱动主要作用

    2023年04月09日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包