Linux: SPI 驱动

news2024/10/5 23:30:58

文章目录

  • 1. 前言
  • 2. SPI 总线驱动
    • 2.1 SPI 总线拓扑
    • 2.2 SPI 总线工作模式
    • 2.3 SPI 总线驱动编写
  • 3. SPI 从设驱动
  • 4. SPI 用户空间接口
    • 4.1 创建 SPI 总线用户空间字符设备节点
    • 4.2 操作 SPI 总线用户字符设备节点

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. SPI 总线驱动

2.1 SPI 总线拓扑

SPI 总线是Master设备和Slave从设通信的接口,是一种高速、全双工的同步串行通信总线。简单看一下 SPI 总线的拓扑结构:
在这里插入图片描述
其中:

SCLK: SPI 总线时钟,4MHz-20MHz;
MOSI: 数据线,从Master传送数据到Slave;
MISO: 数据线,从Slave传送数据到Master;
SSx: Slave片选信号,可能标记成CS更常见。

2.2 SPI 总线工作模式

SPI 有4种工作模式,通过串行时钟极性(CPOL)相位(CPHA)的搭配来得到4种工作模式。先看下 CPOLCPHA 的作用:

1. CPOL=0,串行时钟空闲状态为低电平。
2. CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具体的传输协议。
3. CPHA=0,串行时钟的第1个跳变沿(上升沿或下降沿)采集数据。
4. CPHA=1,串行时钟的第2个跳变沿(上升沿或下降沿)采集数据。

再看由 CPOLCPHA 搭配的模式:

SCL空闲时电平	采样	CPHA	Mode
低电平	上升沿	CPOL = 0, CPHA = 0	Mode0
低电平	下降沿	CPOL = 0, CPHA = 1	Mode1
高电平	下降沿	CPOL = 1, CPHA = 0	Mode2
高电平	上升沿	CPOL = 1, CPHA = 1	Mode3

通过配置 SPI Master控制器,可以配置 SPI 的工作模式,而从设的模式一般是固定的,可以参数从设的数据手册。
SPI 的读写不同于 I2C,不需要显示标记,因为 SPI 是全双工的,读写可以同时进行。

2.3 SPI 总线驱动编写

编写 SPI 总线驱动相关的内核接口:

extern struct spi_controller *__spi_alloc_controller(struct device *host,
						unsigned int size, bool slave);
extern int spi_register_controller(struct spi_controller *ctlr);

看一个 SPI 总线驱动框架示例:

spi@01c68000 {
	compatible = "allwinner,sun8i-h3-spi";
	...

	spi@0 {
		compatible = "nanopi,spidev";
		...
	};
};
static int sun6i_spi_probe(struct platform_device *pdev)
{
	struct spi_master *master;

	/* 创建SPI总线控制器对象 */
	master = spi_alloc_master(&pdev->dev, sizeof(struct sun6i_spi));
	
	...

	/* SPI控制器中断处理: 数据传输完成、传输FIFO等的处理 */
	ret = devm_request_irq(&pdev->dev, irq, sun6i_spi_handler,
			       0, "sun6i-spi", sspi);

	...
	
	master->max_speed_hz = 100 * 1000 * 1000; /* 支持的最小时钟 */
	master->min_speed_hz = 3 * 1000; /* 支持的最小时钟 */
	master->set_cs = sun6i_spi_set_cs; /* 片选接口 */
	master->transfer_one = sun6i_spi_transfer_one; /* 设置SPI总线数据传输接口 */
	...
	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST; /* 设置工作模式 */
	...
	
	/* 注册SPI总线控制器对象到系统 */
	ret = devm_spi_register_master(&pdev->dev, master);

	return 0;
}

static const struct of_device_id sun6i_spi_match[] = {
	...
	{ .compatible = "allwinner,sun8i-h3-spi",  .data = (void *)SUN8I_FIFO_DEPTH },
	{}
};

static struct platform_driver sun6i_spi_driver = {
	.probe	= sun6i_spi_probe,
	...
	.driver	= {
		...
		.of_match_table	= sun6i_spi_match,
		...
	};
};

3. SPI 从设驱动

ads7846 触摸输入设备驱动为例来分析。

spi@01c68000 {
	compatible = "allwinner,sun8i-h3-spi";
	...

	/* SPI 从设 ads7846,挂接在 SPI 总线 spi@01c68000 上 */
	pitft-ts@1 {
		compatible = "ti,ads7846";
		...
		spi-max-frequency = <2000000>;
		interrupt-parent = <&pio>;
        interrupts = <6 9 IRQ_TYPE_EDGE_FALLING>;   /* PG9 / EINT9 */
		...
	};
};
static const struct of_device_id ads7846_dt_ids[] = {
	...
	{ .compatible = "ti,ads7846",	.data = (void *) 7846 },
	...
	{ }
};

/* SPI 从设驱动入口 */
static int ads7846_probe(struct spi_device *spi)
{
	struct input_dev *input_dev;
	
	...
	
	spi->bits_per_word = 8;
	spi->mode = SPI_MODE_0;
	err = spi_setup(spi); /* 设置 SPI 工作模式和时钟频率 */

	/* 创建输入设备对象 */
	input_dev = input_allocate_device();
	...

	/* 配置输入设备 */
	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
	input_set_abs_params(input_dev, ABS_X,
			pdata->x_min ? : 0,
			pdata->x_max ? : MAX_12BIT,
			0, 0);
	...

	/* 上电 */
	ts->reg = regulator_get(&spi->dev, "vcc");
	err = regulator_enable(ts->reg);

	/* 注册输入中断处理接口:上报按键事件 */
	err = request_threaded_irq(spi->irq, ads7846_hard_irq, ads7846_irq,
				   irq_flags, spi->dev.driver->name, ts);
	
	...
	/* 注册输入设备 */
	err = input_register_device(input_dev);

	...
	return 0;
}

static struct spi_driver ads7846_driver = {
	.driver = {
		.name	= "ads7846",
		...
		.of_match_table = of_match_ptr(ads7846_dt_ids),
	},
	.probe		= ads7846_probe,
	...
};

一个 SPI 输入设备的驱动框架已经出来了,现在还剩一点需要说明:系统是何时创建 spi_device 来触发驱动的 ads7846_probe() 接口的?答案是 SPI 控制器驱动对象注册的时候:

#define devm_spi_register_master(_dev, _ctlr) \
	devm_spi_register_controller(_dev, _ctlr)

int devm_spi_register_controller(struct device *dev,
				 struct spi_controller *ctlr)
{
	int ret;

	...

	ret = spi_register_controller(ctlr);
	...

	return ret;
}

int spi_register_controller(struct spi_controller *ctlr)
{
	...
	
	dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);
	status = device_add(&ctlr->dev);

	...

	list_add_tail(&ctlr->list, &spi_controller_list);
	list_for_each_entry(bi, &board_list, list)
		spi_match_controller_to_boardinfo(ctlr, &bi->board_info); /* 旧的 spi_register_board_info() 方式创建 spi_device */

	of_register_spi_devices(ctlr); /* DTS 方式创建 spi_device */
	acpi_register_spi_devices(ctlr); /* ACPI 方式创建 spi_device */
done:
	return status;
}

/* 旧的 spi_register_board_info() 方式创建 spi_device */
static void spi_match_controller_to_boardinfo(struct spi_controller *ctlr,
					      struct spi_board_info *bi)
{
	struct spi_device *dev;

	dev = spi_new_device(ctlr, bi);
	...
}

struct spi_device *spi_new_device(struct spi_controller *ctlr,
				  struct spi_board_info *chip)
{
	struct spi_device	*proxy;

	proxy = spi_alloc_device(ctlr); /* 创建 spi_device 对象 */
	...

	status = spi_add_device(proxy); * 注册 spi_device 到 driver core,触发驱动 probe 接口 */
	...

	return proxy;
}

/* DTS 方式创建 spi_device */
static void of_register_spi_devices(struct spi_controller *ctlr)
{
	struct spi_device *spi;
	
	/*
	 * 扫描 DTS 中 SPI 总线上挂接的从设节点,为它们创建 spi_device。 
	 * 如前面 DTS 代码片段中的 "ti,ads7846" 。
	*/
	for_each_available_child_of_node(ctlr->dev.of_node, nc) {
		...
		spi = of_register_spi_device(ctlr, nc);
		...
	}
}

static struct spi_device *
of_register_spi_device(struct spi_controller *ctlr, struct device_node *nc)
{
	struct spi_device *spi;
	int rc;
	
	spi = spi_alloc_device(ctlr); /* 创建 spi_device 对象 */
	...

	rc = of_spi_parse_dt(ctlr, spi, nc); /* 解析 SPI 从设 DTS 配置 */

	rc = spi_add_device(spi); /* 注册 spi_device 到 driver core,触发驱动 probe 接口 */

	return spi;
}

4. SPI 用户空间接口

我们可以通过 SPI 子系统,提供的用户空间接口来操控 SPI 从设。

4.1 创建 SPI 总线用户空间字符设备节点

spi@01c68000 {
	compatible = "allwinner,sun8i-h3-spi";
	...

	/* SPI 总线用户空间设备 DTS */
	spi@0 {
		compatible = "nanopi,spidev";
		...
	};
};
/* drivers/spi/spidev.c */

static const struct of_device_id spidev_dt_ids[] = {
	...
	{ .compatible = "nanopi,spidev" },
	...
	{},
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);

static int spidev_probe(struct spi_device *spi)
{
	struct spidev_data	*spidev;

	...
	minor = find_first_zero_bit(minors, N_SPI_MINORS);
	if (minor < N_SPI_MINORS) 
		struct device *dev;
	
		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
		/* 创建 SPI 总线的字符设备节点:供用户空间访问 */
		dev = device_create(spidev_class, &spi->dev, spidev->devt,
				    spidev, "spidev%d.%d",
				    spi->master->bus_num, spi->chip_select);
	} else {
		...
	}

	...
}

s
tatic struct spi_driver spidev_spi_driver = {
	.driver = {
		.name =		"spidev",
		.of_match_table = of_match_ptr(spidev_dt_ids),
		...
	},
	.probe =	spidev_probe,
	...
};

static int __init spidev_init(void)
{
	int status;
	
	/* SPI 总线字符设备 */
	status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
	...

	spidev_class = class_create(THIS_MODULE, "spidev");
	...

	status = spi_register_driver(&spidev_spi_driver);
	...

	return status;
}
module_init(spidev_init);

4.2 操作 SPI 总线用户字符设备节点

int fd, mode = SPI_MODE_0;

fd = open("/dev/spidev1.0", O_RDWR);
ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); /* 设置工作模式 */
ret = ioctl(fd_spi, SPI_IOC_WR_BITS_PER_WORD, &bits); /* 设置SPI的数据位 */
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); /* 设置速度 */
ret = ioctl(fd, SPI_IOC_MESSAGE(n), &tr); /* 数据发送 */
...
close(fd);

更多细节参考 drivers/spi/spidev.c 中的 spidev_ioctl() 接口。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/443813.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

百趣代谢组学分享,三阴性乳腺癌铁死亡异质性的揭示

百趣代谢组学分享文章标题&#xff1a;Ferroptosis heterogeneity in triple-negative breast cancer reveals an innovative immunotherapy combination strategy 发表期刊&#xff1a;Cell Metabolism 影响因子&#xff1a;31.373 作者单位&#xff1a;复旦大学附属肿瘤医…

DFS与BFS|树与图的遍历:拓扑排序

深度优先搜索DFS DFS每次往最深处搜&#xff0c;搜到叶子节点就返回&#xff0c;然后继续搜&#xff0c;特点&#xff1a;走到头才返回&#xff0c;返回并不是返回最开始&#xff0c;而是每次返回上一层之后&#xff0c;再看这一层能不能往下搜 DFS有回溯和剪枝。返回上一层的过…

维基百科收录企业或品牌词条的规则解析

作为一家知名企业&#xff0c;开展国外市场营销的前提需要做好海外网络口碑营销&#xff0c;解决品牌信任的问题。小马识途营销顾问近来接到很多关于维基百科的咨询&#xff0c;其实做海外营销的企业倒是很有营销意识&#xff0c;主动寻求创建维基百科的很多&#xff0c;但并不…

138. 复制带随机指针的链表

给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点的值。新节点的 n…

ASEMI代理ADCMP600BKSZ-REEL7原装ADI车规级ADCMP600BKSZ-REEL7

编辑&#xff1a;ll ASEMI代理ADCMP600BKSZ-REEL7原装ADI车规级ADCMP600BKSZ-REEL7 型号&#xff1a;ADCMP600BKSZ-REEL7 品牌&#xff1a;ADI/亚德诺 封装&#xff1a;SC-70-5 批号&#xff1a;2023 引脚数量&#xff1a;5 工作温度&#xff1a;-40C~125C 安装类型&am…

【从零开始学Skynet】实战篇《球球大作战》(四):分布式登录系统

处理玩家的登录&#xff0c;是服务端框架的主要功能之一。分布式系统涉及多个服务&#xff0c;让它们相互配合不产生冲突是一大难点。 1、登录流程 分布式服务端的登录功能要处理好如下两个问题&#xff1a; 问题一&#xff1a;完成角色对象的构建和销毁。如下图所示&#xf…

虹科Panorama SCADA平台连接OPC UA服务器操作详解

一、前言 虹科Panorama SCADA平台支持丰富的通信传输协议&#xff0c;其中包括OPC UA/DA、SNMP、Modbus、BACnet、IEC 61850、MQTT等多种常用的协议类型。OPC UA 采用简单的客户端/服务器的机制进行通信&#xff0c;服务器可在网络中提供大量信息&#xff0c;如有关 CPU、OPC …

pyecharts从入门到精通-地图专题Map-带时间轴与网格的复杂绘图

文章目录 参考安装与查看pyecharts地图实现-Geo导入依赖生成数据集生成2013-2018年的各个省份GDP数据生成2013-2018年的时间列表生成2013-2018年的总GDP设置visulmap的最大最小值范围 生成2013年的网格组合图提取2013年的数据测试绘制map地图绘制折线图line绘制折线图bar绘制折…

图片加水印的简单方法

图片加水印的简单方法~许多小伙伴都习惯在需要对外发布的图片上添加水印&#xff0c;添加水印后就不必担心图片被盗用或被用于其它不良目的&#xff0c;给我们造成不好的影响。然而&#xff0c;许多用户不知道如何为图片添加水印&#xff0c;也不知道应该选择哪种软件来进行操作…

C/C++|物联网开发入门+项目实战|空间读写|非字符空间|返回值内部实现|嵌入式C语言高级|C语言函数的使用(2)-学习笔记(12)

文章目录 空间的读写作用实现strlen实现strcpy 非字符空间void* 返回值返回连续空间类型示例&#xff1a; 函数内部实现示例&#xff1a; 参考&#xff1a; 麦子学院-嵌入式C语言高级-C语言函数的使用 空间的读写 void fun(char *p); const char *p 只读空间&#xff0c;只为…

多功能PDF工具合集:PDF Squeezer - PDF Toolbox Mac

PDF Squeezer - PDF Toolbox Mac是多功能PDF工具合集。专为操作 PDF 文件而设计&#xff0c;包含几乎所有你能想象到的操作&#xff0c;例如&#xff1a;压缩、合并、拆分、提取、插入、提取图像、转换图像、提取文本、排序、加密等。如果你正在为操作PDF文件发愁&#xff0c;快…

关于yolov8的一些理解

文章目录 1.前言2.创新点及工作3. 网络结构3.1 BackBone3.1.1 C2F3.1.2 结构微调3.1.2 SPPF 3.2 Neck3.3 Head 4.正样本匹配策略4.1 静态分配策略&动态分配策略4.2 TaskAlignedAssigner 5.损失函数5.1 概述5.2 Distribution Focal Loss 6.总结 1.前言 YOLOv8 是 ultralyti…

在Eclipse中安装配置JDK11

下载安装包 从请官方下载&#xff0c;地址为&#xff1a;https://www.oracle.com/java/technologies/javase-jdk11-downloads.html&#xff0c;选择相应的版本下载即可。注意&#xff0c;现在需要注册才能下载。 ** ** 安装JDK11 使用下载的安装包进行安装&#xff0c;双击可…

Hyperledger Fabric 2.x 环境搭建

一、说明 区块链网络的核心是分布式账本&#xff0c;在这个账本中记录了网络中发生的所有交易信息。 Hyperledger Fabric 是一个是开源的&#xff0c;企业级的&#xff0c;带权限的分布式账本解决方案的平台。Hyperledger Fabric 由模块化架构支撑&#xff0c;并具备极佳的保…

拉格朗日粒子扩散模式FLEXPART

为了高效、精准地治理区域大气污染&#xff0c;需要弄清污染物的来源。拉格朗日粒子扩散模式FLEXPART通过计算点、线、面或体积源释放的大量粒子的轨迹&#xff0c;来描述示踪物在大气中长距离、中尺度的传输、扩散、干湿沉降和辐射衰减等过程。该模式既可以通过时间的前向运算…

Pytorch基础 - 7. torch.transpose() 和 torch.permute()

目录 1. torch.transpose(dim0, dim1) 2. torch.permute(dims) 3. 在转置函数之后使用view() PyTorch中使用torch.transpose() 和 torch.permute()可对张量进行维度的转置&#xff0c;具体内容如下&#xff1a; 1. torch.transpose(dim0, dim1) 参数: 需要转置的两个维度…

React native RN 开发实例

多入口加载方式 React Native 混合开发多入口加载方式 - 知乎 initialProperties 官方文档&#xff1a;React Navigation moduleName 案例&#xff1a;GitHub - hcn1519/ReactNative-iOS-Example React Native 混合开发多入口加载方式 - 知乎 项目结构 参考&#xff1…

传奇修改怪物属性教程:传奇服务端架设后怪物不主动攻击是什么原因?

当你架设好传奇服务端后怪物不主动攻击是什么原因呢&#xff1f;今天飞飞来和你分享 1、检查是否进入了管理员隐藏模式 如果是你设置了GM号进入了隐身模式&#xff0c;登录游戏就会有登录脚本默认管理上线检测&#xff0c;怪物看到你是GM管理员自然不会攻击你的&#xff0c;你…

(顶级理解)为什么Vue中的v-if 和v-for不建议一起用?

目录 1.背景 2.原因 3.通俗案例 4.解决方案 5.扩展&#xff08;强烈看下&#xff09; 1.背景 我们都知道v-if和v-for是vue开发工程中十分常用的方法 2.原因 当 v-if 与 v-for 一起使用时&#xff0c;v-for 具有比 v-if 更高的优先级。这意味着 v-if 将分别重复运行于 每…

jQuery 基础入门速成下篇

jQuery高级 事件冒泡 什么是事件冒泡&#xff1f; 在一个对象上触发某类事件&#xff0c;此对象上定义了此事件的处理程序&#xff0c;那么此事件就会调用这个处理程序&#xff0c;如果没有定义此事件处理程序或事件返回true&#xff0c;那么这个事件会向这个对象的父级对象传…