linux spi驱动

https://blog.youkuaiyun.com/chenliang0224/article/details/51057109

最近在学习Linux spi驱动,中途出现了诸多疑问,天苍苍野茫茫,坚持总是可以看到牛羊的,本文以新唐NUC972这颗芯片为例进行逐步分析
 
参考很有价值的两篇文章:
	http://www.th7.cn/system/lin/201507/122488.shtml
	http://blog.chinaunix.net/uid-25445243-id-4026974.html
	
1、SPI 总线注册
	源码路径:drivers/spi/spi.c
	有兴趣的可以看下这位大神的文章,很详细http://blog.chinaunix.net/uid-25445243-id-4032371.html
	
2、spidev.c
	源码路径:drivers/spi/spidev.c
	如果不想写具体的芯片驱动,就可以用kernel自带的驱动spidev.c(这里是困扰我的地方,之前一直没搞明白这里有了
	spi驱动程序,为啥还要编写,因为某个设备对应的驱动必须是唯一的,而驱动却可以对应多个设备),在板级信息spi_board_info
	结构体中将.modalias = "spidev"即可,因为spidev.c中已经驱动取名为“spidev”
 
3、SPI platform_device设备注册 
	源码路径:arch/arm/mach-nuc970/dev.c
	
	static struct nuc970_spi_info nuc970_spi0_platform_data = {
        .num_cs		= 1,
        .lsb		= 0,
        .txneg		= 1,
        .rxneg		= 0,
        .divider	= 4,
        .sleep		= 0,
        .txnum		= 0,
        .txbitlen	= 8,
        .bus_num	= 0,	//注意这个参数很重要!表示SPI控制器的编号是0,将来在spi从设备的
							//板级信息中有体现,意思是将来这个spi从设备挂在编号为0的spi总线下面;
	};
	
	static struct resource nuc970_spi0_resource[] = {
        [0] = {
                .start = NUC970_PA_SPI0,
                .end   = NUC970_PA_SPI0 + NUC970_SZ_SPI0 - 1,
                .flags = IORESOURCE_MEM,
        },
        [1] = {
                .start = IRQ_SPI0,
                .end   = IRQ_SPI0,
                .flags = IORESOURCE_IRQ,
        }
	};
	
	struct platform_device nuc970_device_spi0 = {
        .name		  = "nuc970-spi0",	//设备名
        .id		  = -1,
        .num_resources	  = ARRAY_SIZE(nuc970_spi0_resource),
        .resource	  = nuc970_spi0_resource,
		#if defined(CONFIG_MTD_M25P80) || defined(CONFIG_SPI_SPIDEV)
        .dev		= {
                .platform_data = &nuc970_spi0_platform_data,
		}
		#endif
	};
	
	static struct platform_device *nuc970_public_dev[] __initdata = {
        &nuc970_device_spi0,
	}
 
	//注册spi设备
	void __init nuc970_platform_init(struct platform_device **device, int size)
	{
		platform_add_devices(device, size);
		platform_add_devices(nuc970_public_dev, ARRAY_SIZE(nuc970_public_dev));
	}
 
4、SPI platform_driver驱动注册
	源代码路径:drivers/spi/spi-nuc970-p0.c
	
	static struct platform_driver nuc970_spi0_driver = {
		.probe		= nuc970_spi0_probe,	//由于platform_device先于platform_driver注册,所以设
											//备.name=设备驱动.name时将回调nuc970_spi0_probe
		.remove		= nuc970_spi0_remove,
		.driver		= {
			.name	= "nuc970-spi0",
			.owner	= THIS_MODULE,
		},
	};
	
	//注册spi驱动
	module_platform_driver(nuc970_spi0_driver);
 
	
5、SPI从设备板级信息 以M25P80 norflash芯片为例
	源码路径:arch/arm/mach-nuc970/dev.c
	
	#if defined(CONFIG_SPI_FLASH_W25Q)
	 static struct gsc3280_spi_info w25q_spi1_dev_platdata = {
		 .cs_type            = 1,
		.pin_cs            = 87,
		.num_cs            = 1,
		.cs_value            = 0,
		.lsb_flg            = 0,
		.bits_per_word    = 8,
	};
	#endif
	static struct spi_board_info gsc3280_spi_devices[] = {
	#if defined(CONFIG_SPI_FLASH_W25Q)
		{
			.modalias        = "spi-w25q",
			.bus_num        = 0,//注意这个参数很重要(参考第3点解释)!这个从设备将挂在编号为0的总线上,
								//这样从设备“spi-w25q”就与“nuc970-spi0”平台总线(控制器驱动)连接起来了
			.chip_select        = 2,
			.mode            = SPI_MODE_3,
			.max_speed_hz    = 5 * 1000 * 1000,
			.controller_data    = &w25q_spi1_dev_platdata,
		},
	#endif
	};
	
	//注册“spi-w25q”板级信息
	static int __init gsc3280_spi_devices_init(void)
	{
		spi_register_board_info(gsc3280_spi_devices, ARRAY_SIZE(gsc3280_spi_devices));
		return 0;
	}
	device_initcall(gsc3280_spi_devices_init);
	
6、SPI 从设备驱动程序(以字符设备,att7022e就是通过这里进行操作的)
	static struct spi_driver w25q_driver = {
		.driver    = {
			.name    = "spi-w25q",
			.owner    = THIS_MODULE,
		},
		//.id_table    = w25q_ids,
		.probe    = w25q_probe,
		.remove    = __devexit_p(w25q_remove),
	};
 
 
	static int __init w25q_init(void)
	{
		return spi_register_driver(&w25q_driver);
	}
	static void __exit w25q_exit(void)
	{
		spi_unregister_driver(&w25q_driver);
	}
	module_init(w25q_init);
	module_exit(w25q_exit);
	
	......
	
	花了一个三天的时间才搞明白SPI驱动的注册原理。。。

 

<think> 我们参考了多个引用资料,这些资料详细介绍了Linux系统中SPI驱动框架的各个组成部分及其相互关系。根据引用内容,我们可以将SPI驱动框架分为三个核心部分:SPI核心层(SPI Core)、SPI主机驱动SPI Master Driver)和SPI设备驱动SPI Device Driver)。下面我们将逐一介绍这三个部分,并说明它们如何协同工作。 ### 1. SPI核心层(SPI Core) SPI核心层是Linux SPI驱动框架的中间层,它提供了一系列通用的接口和函数,用于连接SPI主机驱动SPI设备驱动。其主要功能包括: - 注册SPI总线(`spi_bus_type`)以及管理总线上的设备与驱动匹配。 - 提供SPI控制器(主机)和SPI设备驱动的注册和注销接口。 - 提供数据传输的通用函数(如`spi_sync`, `spi_async`等),供设备驱动使用。 引用[3]提到:“SPI核心层是SPI子系统的中间层,提供了一个通用的接口以便设备驱动可以与SPI主机驱动交互。” 同时,引用[2]也指出SPI核心层负责“注册SPI总线以及匹配总线与设备”。 ### 2. SPI主机驱动SPI Master Driver) SPI主机驱动直接与硬件SPI控制器交互,负责底层的硬件操作。在Linux内核中,SPI主机驱动由`spi_master`结构体表示(引用[5])。主要职责包括: - 初始化SPI控制器硬件(如时钟、引脚等配置)。 - 实现数据传输的具体方法(如`transfer`函数)。 - 通过SPI核心层注册到系统中,以便设备驱动可以访问。 引用[1]提到:“spi_master负责管理SPI总线,为spi_device提供通信的基础。” 引用[5]进一步指出:“SPI主机驱动就是SOC的SPI控制器驱动,类似I2C驱动里面的适配器驱动。” ### 3. SPI设备驱动SPI Device Driver) SPI设备驱动针对具体的SPI从设备(如传感器、存储器等),通过`spi_driver`结构体实现(引用[4])。主要工作包括: - 定义设备ID表(`id_table`)用于与设备匹配。 - 实现`probe`、`remove`等函数,用于设备的初始化和卸载。 - 通过SPI核心层提供的接口(如`spi_read`、`spi_write`)与设备通信。 引用[4]给出了`spi_driver`结构体的定义,并说明我们在编写设备驱动时需要实现这个结构体。 ### 4. SPI设备(SPI Device) 除了上述三个核心部分,还有一个重要的概念是SPI设备(`spi_device`),它代表一个具体的SPI从设备。在设备树(Device Tree)中描述,并在系统启动时由内核解析并创建。`spi_device`包含该设备的配置信息(如最大速度、模式等)。 ### 协同工作流程 1. **注册SPI主机驱动**:SPI控制器驱动程序(主机驱动)在初始化时,通过`spi_register_master()`函数向SPI核心层注册自己。 2. **注册SPI设备**:设备树中定义的SPI从设备会在系统启动时被内核解析,并创建对应的`spi_device`,然后注册到SPI总线上。 3. **注册SPI设备驱动**:设备驱动通过`spi_register_driver()`注册自己,其`id_table`会与已注册的`spi_device`进行匹配。 4. **匹配与探测**:当匹配成功时,设备驱动的`probe`函数被调用,完成设备初始化。 5. **数据传输**:设备驱动通过SPI核心层提供的接口(如`spi_sync_transfer()`)发起数据传输请求,该请求最终会调用主机驱动实现的`transfer`函数。 ### 开发SPI设备驱动的步骤 1. **定义`spi_driver`结构体**:设置`id_table`、`probe`、`remove`等函数。 2. **实现`probe`函数**:在`probe`函数中初始化设备,例如配置寄存器、申请资源、注册字符设备等。 3. **实现数据传输函数**:使用SPI核心层提供的API(如`spi_write_then_read()`)与设备通信。 4. **注册驱动**:在模块初始化函数中调用`spi_register_driver()`。 5. **注销驱动**:在模块退出函数中调用`spi_unregister_driver()`。 ### 示例代码片段 以下是一个简单的SPI设备驱动示例(伪代码): ```c #include <linux/spi/spi.h> static int my_spi_probe(struct spi_device *spi) { // 获取设备配置(如模式、速度等) spi->mode = SPI_MODE_0; spi->max_speed_hz = 1000000; spi_setup(spi); // 设备初始化操作 // ... return 0; } static int my_spi_remove(struct spi_device *spi) { // 清理资源 // ... return 0; } static const struct spi_device_id my_spi_id_table[] = { { "my_spi_device", 0 }, { } }; MODULE_DEVICE_TABLE(spi, my_spi_id_table); static struct spi_driver my_spi_driver = { .driver = { .name = "my_spi_driver", .owner = THIS_MODULE, }, .id_table = my_spi_id_table, .probe = my_spi_probe, .remove = my_spi_remove, }; module_spi_driver(my_spi_driver); // 自动注册和注销驱动 ``` ### 总结 Linux SPI驱动框架通过分层设计,将硬件控制(主机驱动)、设备驱动和核心层分离,提高了代码的复用性和可维护性。开发人员通常只需关注设备驱动的实现,而无需直接操作硬件控制器。 §§相关问题§§ 1. 如何在设备树中描述一个SPI设备节点? 2. SPI主机驱动中的`transfer`函数需要实现哪些功能? 3. SPI设备驱动中如何实现同步和异步数据传输? 4. 如何调试Linux SPI驱动? </think>### Linux SPI 驱动框架详解 Linux SPI 驱动采用分层架构设计,主要由三个核心组件构成: #### 1. **SPI主机控制器驱动spi_master)** - **作用**:管理物理SPI控制器硬件 - **功能**: - 初始化SPI控制器时钟/寄存器 - 实现底层数据传输接口(如`transfer()`函数) - 提供总线访问能力 - **数据结构**: ```c struct spi_master { struct device dev; int (*transfer)(struct spi_device *spi, struct spi_message *mesg); // 其他硬件操作函数 }; ``` - **注册方式**:`spi_register_master()`[^5] #### 2. **SPI设备抽象(spi_device)** - **作用**:描述连接的SPI从设备 - **配置信息**: - 片选号(CS) - 通信模式(CPOL/CPHA) - 最大时钟频率 - **创建方式**: - 设备树描述(推荐): ```dts &spi1 { status = "okay"; sensor@0 { compatible = "vendor,spi-sensor"; reg = <0>; // 片选号 spi-max-frequency = <1000000>; }; }; ``` - 或通过`spi_new_device()`动态创建[^1] #### 3. **SPI设备驱动spi_driver)** - **作用**:实现具体设备的功能逻辑 - **核心结构**: ```c struct spi_driver { const struct spi_device_id *id_table; int (*probe)(struct spi_device *spi); int (*remove)(struct spi_device *spi); struct device_driver driver; }; ``` - **关键操作**: - **probe()**:设备初始化,资源分配 - **remove()**:资源释放 - 实现文件操作接口(如read/write)[^4] ### 数据传输流程 1. **构造消息**: 使用`spi_message`结构封装传输请求 ```c struct spi_transfer xfer = { .tx_buf = tx_data, .rx_buf = rx_data, .len = len }; spi_message_init(&msg); spi_message_add_tail(&xfer, &msg); ``` 2. **提交传输**: 同步传输:`spi_sync(spi, &msg)` 异步传输:`spi_async(spi, &msg)` 3. **硬件执行**: `spi_master->transfer()` 被调用,控制器处理物理信号[^5] ### 驱动开发步骤 1. **注册驱动**: ```c static struct spi_driver my_driver = { .driver = { .name = "my_spi_dev" }, .probe = my_probe, .remove = my_remove, .id_table = my_spi_ids, }; module_spi_driver(my_driver); ``` 2. **实现probe函数**: ```c static int my_probe(struct spi_device *spi) { // 1. 验证配置 spi->mode = SPI_MODE_0; spi_setup(spi); // 2. 分配设备数据 struct my_data *data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL); // 3. 初始化设备(如寄存器配置) spi_write(spi, init_seq, sizeof(init_seq)); // 4. 注册字符设备/创建sysfs节点 misc_register(&my_miscdev); } ``` 3. **实现数据传输**: ```c uint8_t read_reg(struct spi_device *spi, uint8_t reg) { uint8_t tx[2] = {reg, 0}; uint8_t rx[2]; struct spi_transfer t = { .tx_buf = tx, .rx_buf = rx, .len = 2, }; spi_sync_transfer(spi, &t, 1); return rx[1]; } ``` ### 关键API函数 | 函数 | 作用 | |------|------| | `spi_write()` | 同步写操作 | | `spi_read()` | 同步读操作 | | `spi_write_then_read()` | 先写后读组合操作 | | `spi_sync_transfer()` | 高级传输控制 | | `spi_message_init()` | 初始化消息结构 | ### 调试技巧 1. 启用SPI层调试:`echo 1 > /sys/module/spi/parameters/debug` 2. 查看注册的设备:`cat /sys/bus/spi/devices/*/uevent` 3. 使用逻辑分析仪抓取SCK/MOSI/MISO信号
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值