设备树中的spi子系统

spi节点一般表示spi控制器, 它会被转换为platform_device, 全志H3的总线设备驱动位于busses/spi-sun6i.c,该文件有对应的platform_driver;platform_driver的probe函数中会调用i2c_add_numbered_adapter,来增加一个spi_master,调用过程如下:

sun6i_spi_probe
	devm_spi_register_master
		devm_spi_register_controller
			spi_register_controller
				of_register_spi_devices(ctlr); /* Register devices from the device tree  */
					for_each_available_child_of_node(ctlr->dev.of_node, nc)
						spi = of_register_spi_device(ctlr, nc);
			

 

由上面的调用流程可以得知,spi节点遍历其下的子节点,转换成一个个spi_device 在spi总线驱动程序中,才会处理SPI的设备树节点,找到处理代码, 通过of_register_spi_device创建SPI设备

 

在未使用设备树描述spi设备时,spi设备的平台信息由spi_register_board_info来注册 相关函数调用如下:

drivers/spi/spi.c:
    spi_register_board_info
        /* 对于每一个spi_master,调用spi_match_master_to_boardinfo */
        list_for_each_entry(master, &spi_master_list, list)
            spi_match_master_to_boardinfo
            /* board_info里含有bus_num, 如果某个spi_master的bus_num跟它一样
             * 则创建一个新的spi_device
             */
                    if (master->bus_num == bi->busnum)
                        spi_new_device
                            spi_alloc_device
                            /* 记录bi信息, 比如片选,MODE,MAX HZ */    
                            spi_add_device /* 根据名字找到spi_driver, 调用它的probe函数 */
                                spi_setup(spi);
                                device_add  /* 会绑定到一个spi_driver */

 

在全志H3平台上,SPI总线和SPI设备的平台信息都是由设备树来描述。通过反编译sun8i-h3-nanopi-m1.dtb可以得出设备树中的spi节点

spi@01c68000 {
			
	compatible = "allwinner,sun8i-h3-spi";
			
	reg = <0x1c68000 0x1000>;
			
	interrupts = <0x0 0x41 0x4>;
			
	clocks = <0x4 0x1e 0x4 0x52>;
			
	clock-names = "ahb", "mod";
			
	dmas = <0x7 0x17 0x7 0x17>;
			
	dma-names = "rx", "tx";
			
	pinctrl-names = "default";
			
	pinctrl-0 = <0x1e 0x1f>;
			
	resets = <0x4 0xf>;
			
	status = "okay";
			
	#address-cells = <0x1>;
			
	#size-cells = <0x0>;
			
	cs-gpios = <0x14 0x2 0x3 0x0 0x14 0x0 0x6 0x0>;
			
	linux,phandle = <0x61>;
			
	phandle = <0x61>;

			
	spi@0 {
				
		compatible = "nanopi,spidev";
	/* 此属性值用于与spi设备驱动匹配 */			
		reg = <0x0>;
	     //第0个片选			
		status = "okay";
				
		spi-max-frequency = <0x989680>;
	  //最大频率10M			
		linux,phandle = <0x62>;
				
		phandle = <0x62>;
			
	};

			
	spiflash@0 {
				
		#address-cells = <0x1>;
				
		#size-cells = <0x1>;
				
		compatible = "mxicy,mx25l12805d";
				
		reg = <0x0>;
				
		status = "disabled";
				
		spi-max-frequency = <0x2faf080>;
				
		mode = <0x0>;
				
		linux,phandle = <0x63>;
				
		phandle = <0x63>;

				
		partition@0 {
					
			reg = <0x0 0x1000000>;
					
			label = "spi-flash";
				
};
			};

			
		pitft@0 {
				
		compatible = "sitronix,st7789v";
				
		reg = <0x0>;
				
		status = "disabled";
				
		spi-max-frequency = <0x2faf080>;
				
		rotate = <0x5a>;
					
		fps = <0x21>;
				
		buswidth = <0x8>;
				
		dc-gpios = <0x14 0x0 0x1 0x0>;
				
		reset-gpios = <0x14 0x6 0xb 0x0>;
				
		debug = <0x0>;
				
		linux,phandle = <0x64>;
				
		phandle = <0x64>;
			
	};

			
	pitft-ts@1 {
				
		compatible = "ti,ads7846";
				
		reg = <0x1>;
				
		status = "disabled";
				
		spi-max-frequency = <0x1e8480>;
				
		interrupt-parent = <0x14>;
					
		interrupts = <0x6 0x9 0x2>;
				
		pendown-gpio = <0x14 0x6 0x9 0x1>;
				
		ti,swap-xy;
				
		ti,vref-delay-usecs = <0x3e8>;
				
		ti,x-min = [00 64];
				
		ti,x-max = [0f ff];
				
		ti,y-min = [00 64];
				
		ti,y-max = [0f ff];
				
		ti,vref-mv = <0xce4>;
				
		ti,x-plate-ohms = [01 00];
				
		ti,penirq-recheck-delay-usecs = <0xa>;
				
		ti,settle-delay-usec = [00 64];
			
		ti,keep-vref-on = <0x1>;
				
		ti,pressure-max = [0f ff];
				
		ti,debounce-max = <0xa>;
				
		ti,debounce-tol = <0x1e>;
				
		ti,debounce-rep = <0x1>;
				
		linux,phandle = <0x65>;
				
		phandle = <0x65>;
			
	};	
};

硬件信息说明(linux4.14,soc是nanopi-m1)

共阳数码管使用的是 3641BS ,数码管模块的原理图如下所示,通过两片74HC595芯片分别控制数码管的位选和段选,位选高电平有效 段选低电平有效,这样就可以点亮数码管。

 

流程 :

发送8位位选数据

发送8位段选数据

 

全志H3两个spi控制器,相关数码管模块中的MOSI,SCK,CS接在PC0,PC2,PC3。由于数码管不会返回数据给soc。因此不需要使用MISO。

SPI0:

MOSI======>>> PC0

MISO======>>> PC1

SCK ======>>> PC2

CS ======>>> PC3

使用spi0通过控制74HC595锁存器来达到控制八段数码管的目的

在sun8i-h3-nanopi.dtsi中引用了spi0节点,并在其下构造了spidev0子节点,首先我们把它的状态disable掉,spi0外接74HC595的相关信息通过编写驱动来注册spi_register_board_info

359     spidev0: spi@0 {
360         compatible = "nanopi,spidev";
361         reg = <0>;
362         status = "disable";
363
364         spi-max-frequency = <10000000>;
365     };

 

编译设备树文件,把dtb文件拷贝到启动卡的第一个分区,重启开发板。进入文件系统

查看sysfs没有spi 设备节点

root@wu:~# ls /sys/bus/spi/devices/
root@wu:~#
root@wu:~# ls /dev/spidev0.0
/dev/spidev0.0

 

可以通过/dev/spidev0.0从用户空间访问

使用总线设备驱动模型来编写spi设备驱动

  • 编写spi_info_595.c,设置spi_board_info结构体,通过spi_board_info注册到系统
  • static struct spi_board_info spi_info_smg[] = {
    	{
        	 .modalias = "595_smg",  /* 对应的spi_driver名字也是"595_smg" */
        	 .max_speed_hz = 10000,	/* max spi clock (SCK) speed in HZ */
        	 .bus_num = 0,     /* nanopi里smg接在SPI CONTROLLER 0 */
        	 .mode    = SPI_MODE_0,
        	 .chip_select   = 0, /* 片选编号 */
        	 //.platform_data = (const void *)S3C2410_GPG(4) , /* oled_dc, 它在spi_driver里使用 */
    	 },
    };
    static int spi_info_smg_init(void)
    {
        return spi_register_board_info(spi_info_smg, ARRAY_SIZE(spi_info_smg));
    }

     

 

  • 编写spi_drv_595.c
    • 设置注册一个spi_driver结构体,当spi_driver里的name和spi_board_info里的modalias字段相匹配时,则会调用spi_driver结构体里的probe函数
static struct spi_driver spi_smg_drv = {
	.driver = {
		.name	= "595_smg",
		.owner	= THIS_MODULE,
	},
	.probe		= spi_smg_probe,
	.remove		= spi_smg_remove,
};

static int spi_smg_init(void)
{
    return spi_register_driver(&spi_smg_drv);
}

 

    • 在probe函数中获得得到在总线驱动程序中解析得到的spi_device,并为该设备注册一个字符设备
static struct file_operations smg_ops = {
	.owner            = THIS_MODULE,
	.open   		  = smg_open,
	.write            = smg_write,
};

static int  spi_smg_probe(struct spi_device *spi)
{
    spi_smg_dev = spi;

	printk("595_msg probed\n");

    /* 注册一个 file_operations */
    major = register_chrdev(0, "595_smg", &smg_ops);

	class = class_create(THIS_MODULE, "595_smg");

	/* 为了让mdev根据这些信息来创建设备节点 */
	device_create(class, NULL, MKDEV(major, 0), NULL, "595_smg"); /* /dev/595_smg */
    return 0;
}

 

  • 填充字符设备中的file_operations结构体 ,在驱动的write函数中,通过spi写入数码管对应的位码和段码
static ssize_t smg_write(struct file *file,
				      const char __user *buf,
				      size_t count, loff_t *ppos)
{
    int ret;
	unsigned char data[2];
    if (count > 2)
        return -EINVAL;
    ret = copy_from_user(data, buf, count);
   write_test(data, 2);
    return 1;
}

int smg_open (struct inode *inode, struct file *file)
{
	printk("drv open\n");
	return 0;
}

 

  • 构造spi_message,通过异步传输给spi总线
static int write_test(unsigned char *buffer, int len)
{
    int status;
    struct spi_transfer    t = {
        .tx_buf        = buffer,
        .len        = len,
    };
    struct spi_message    m;
    spi_message_init(&m);
    spi_message_add_tail(&t, &m);
    DECLARE_COMPLETION_ONSTACK(done);
    m.complete = complete;
    m.context = &done;

    printk("spi_async send begin!\n");
    status = spi_async(spi_smg_dev,&m);
    if(status == 0){
        wait_for_completion(&done);
        status = m.status;
        if (status == 0)
            status = m.actual_length;
    }
    return status;
}

 

编写驱动程序的Makefile

KERN_DIR =/samba_share/nanopi_4.14/linux

all:
        make -C $(KERN_DIR) M=`pwd` modules

clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf modules.order

obj-m   += spi_info_595.o
obj-m   += spi_drv_595.o

 

编译成功后,挂载nfs,加载spi_info_595.ko这一模块,出现Unknown symbol spi_register_board_info,这是因为内核中没有导出该符号 drivers/spi/spi.c文件里找到spi_register_board_info函数实现的地方,在后面加上一句:EXPORT_SYMBOL_GPL(spi_register_board_info);

重新编译内核即可。

加载spi_info_595.ko这一模块,成功后在sysfs文件下出现spi设备

root@wu:/mnt/spi_test/spidrv_nodt# ls /sys/bus/spi/devices/
spi0.0

 

加载spi_drv_595.ko这一模块,就会和注册上的设备相互匹配

root@wu:/mnt/spi_test/spidrv_nodt# insmod spi_drv_595.ko
[  288.557768] 595_msg probed

 

编写测试程序

#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>

uint8_t led_value_table[] =   {0xC0, 0xF9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90};
uint8_t led_num_table[]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};

void show_led_num(int fd,int value,int num)
{
	int ret;
	uint8_t tx[]={led_value_table[value],led_num_table[num],};

	write(fd,tx,2);
}


int main(int argc, char **argv)
{
	int fd;
	char val;
	unsigned char buf[3];
	int led_value,led_num;

	if(argc!=3)
			{
				printf("cmd : ./spi_usrspace led_value led_num \n ");
				return -1;
		}
		led_value=atoi(argv[1]);
		if ((led_value) < 0 || (led_value > 9)) { /* 该值必须在 0 ~ 9 之间 */
				printf("led num just in 0 ~ 9 \n");
				return -1;
		}
		led_num = atol(argv[2]); /* 获取程序输入参数的数字选择值 */
		if ((led_num < 0) || (led_num > 7)) { /* 该值必须在 0 ~ 9 之间 */
			printf("led number just in 0 ~ 7");
			return -1;
		}

	fd = open("/dev/595_smg", O_RDWR);
		sleep(1);

	show_led_num(fd,led_value,led_num);
	return 0;
}

 

交叉编译后,数码管成功显示数字

 

 

编写基于设备树下spi设备驱动程序,平台信息有设备树来指定

 

在sun8i-h3-nanopi.dtsi中引用了spi0节点,现在sun8i-h3-nanopi-m1.dts在增加一个节点

&spi0{
    status="okay";
    595@0 {
            compatible = "hc,595";
            reg = <0>;
            status = "okay";
            spi-max-frequency = <100000>;
    };
};

 

编译设备树,把dtb文件拷贝到启动卡的第一个分区,重启开发板。进入文件系统

root@wu:~# ls /sys/bus/spi/devices/
spi0.0
root@wu:~# cat /sys/bus/spi/devices/spi0.0/of_node/compatible
hc,595

 

编写驱动程序

  • 构造一个platform_driver,其中的of_match_table字段需要与 595@0节点的compatible属性值一致,当匹配时则调用platform_driver的probe函数
static const struct of_device_id ids[]=
{
	{.compatible="hc,595"},
	{}
};
static struct spi_driver spi_smg_drv = {
	.driver = {
		.name	= "wu",
		.owner	= THIS_MODULE,
		.of_match_table=ids,
	},
	.probe		= spi_smg_probe,
	.remove		= spi_smg_remove,
};

static int spi_smg_init(void)
{
    return spi_register_driver(&spi_smg_drv);
}

 

 

  • 在probe函数中获得得到在总线驱动程序中解析得到的spi_device,并为该设备注册一个字符设备
static struct file_operations smg_ops = {
	.owner            = THIS_MODULE,
	.open   		  = smg_open,
	.write            = smg_write,
};

static int  spi_smg_probe(struct spi_device *spi)
{
    spi_smg_dev = spi;

	printk("595_msg probed\n");


    /* 注册一个 file_operations */
    major = register_chrdev(0, "595_smg", &smg_ops);

	class = class_create(THIS_MODULE, "595_smg");

	/* 为了让mdev根据这些信息来创建设备节点 */
	device_create(class, NULL, MKDEV(major, 0), NULL, "595_smg"); /* /dev/595_smg */

 

  • 填充字符设备中的file_operations结构体 ,在驱动的write函数中,通过spi写入数码管对应的位码和段码
static ssize_t smg_write(struct file *file,
				      const char __user *buf,
				      size_t count, loff_t *ppos)
{
    int ret;
	unsigned char data[2];
    if (count > 2)
        return -EINVAL;
    ret = copy_from_user(data, buf, count);
   write_test(data, 2);
    return 1;
}

int smg_open (struct inode *inode, struct file *file)
{
	printk("drv open\n");
	return 0;
}

 

  • 在platform_driver的remove函数中,注销该字符设备
static int  spi_smg_remove(struct spi_device *spi)
{

	device_destroy(class, MKDEV(major, 0));
	class_destroy(class);
	unregister_chrdev(major, "595_smg");


	return 0;
}

 

使用上面的测试程序,进行测试,显示正常

<think>好的,我现在需要帮助用户解答关于LinuxSPI设备树驱动开发的问题。首先,用户明确提到了设备树配置,所以我要确保回答中包含设备树的相关内容。接下来,我需要回忆一下Linux驱动开发的基本流程,尤其是SPI驱动的部分。用户可能已经了解一些驱动开发的基础,但可能需要具体的步骤和示例。我应该分步骤说明,从设备树配置开始,然后到驱动模块的编写,最后是加载和测试。这样结构清晰,用户容易跟随。 设备树部分,需要解释如何添加SPI节点,包括兼容性字符串、寄存器地址、时钟频率等。同时,要提到具体的示例代码,比如spi_icm20608这样的设备节点,这样用户有参考的实例。记得使用正确的LaTeX格式,行内公式用$...$,独立公式用$$...$$,不过这个问题可能涉及代码较多,数学公式较少,但还是要检查是否有需要的地方。接下来是驱动模块的开发,这里需要提到probe函数、初始化流程,比如分配内存、注册字符设备、创建设备节点等。引用中的probe函数示例可能有用,可以引用[^3]来说明。同时,要提到SPI核心API,如spi_setup、spi_sync等,这些都是关键点。然后,用户可能需要知道如何编译和加载驱动模块,这部分要给出具体的命令,比如insmod或者modprobe,以及如何将模块复制到开发板的系统目录,引用[^1]中的路径示例可能会有帮助。此外,测试部分需要说明如何通过应用程序与驱动交互,或者使用sysfs进行简单测试。最后,相关问题的生成需要考虑用户可能的后续疑问,比如设备树语法、调试方法、用户空间交互以及实际项目案例。这些问题能够帮助用户深入学习和解决问题。 需要确保回答中的引用正确,比如设备树配置引用[^2],驱动代码结构引用[^3],路径操作引用[^1]。同时,语言要简洁明了,避免过于技术化的术语,但又要准确无误。检查是否有遗漏的步骤,比如设备树的编译和加载,这在开发过程中很重要,必须提及。 总结一下,回答的结构应该是:设备树配置 ->驱动开发步骤 ->编译加载->测试验证->参考资料和相关问题。确保每个部分都有示例代码或命令,这样用户可以直接应用。同时,注意使用中文,符合用户要求,并且LaTeX格式正确,虽然这个问题可能涉及代码较多,但数学公式可能较少,但如果有的话要正确应用。</think>### Linux SPI设备树驱动开发教程 #### 一、设备树配置 在Linux SPI驱动开发中,**设备树(Device Tree)** 是硬件描述的核心。以下是典型SPI设备节点的配置示例: ```dts &spi1 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&spi1_pins>; cs-gpios = <&gpio 20 GPIO_ACTIVE_LOW>; icm20608: icm20608@0 { compatible = "invensense,icm20608"; // 驱动匹配标识[^3] reg = <0>; // 片选信号编号 spi-max-frequency = <10000000>; // SPI时钟频率 interrupt-parent = <&gpio>; interrupts = <21 IRQ_TYPE_EDGE_RISING>; }; }; ``` - **关键参数**: - `compatible`:与驱动代码中的`of_device_id`匹配 - `spi-max-frequency`:定义通信速率(单位:Hz) - `reg`:指定片选信号编号(对应`cs-gpios`数组索引) 设备树编译后需通过`bootloader`加载到内核[^2]。 --- #### 二、驱动开发步骤 1. **驱动框架定义** ```c static const struct of_device_id spi_ids[] = { { .compatible = "invensense,icm20608" }, // 匹配设备树节点[^3] {} }; static struct spi_driver my_spi_driver = { .probe = spi_probe, .remove = spi_remove, .driver = { .name = "icm20608", .of_match_table = spi_ids, } }; ``` 2. **Probe函数实现** ```c static int spi_probe(struct spi_device *spi) { struct icm20608_dev *dev; // 1. 分配设备内存 dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL); // 2. 初始化SPI通信参数 spi->mode = SPI_MODE_0; // 时钟相位/极性配置 spi_setup(spi); // 应用参数设置[^3] // 3. 注册字符设备 alloc_chrdev_region(&dev->devno, 0, 1, "icm20608"); cdev_init(&dev->cdev, &fops); cdev_add(&dev->cdev, dev->devno, 1); // 4. 创建设备节点 dev->class = class_create(THIS_MODULE, "icm20608"); device_create(dev->class, NULL, dev->devno, NULL, "icm20608"); // 5. 数据收发示例 u8 tx_buf[2] = {0x75, 0x00}; // 读取WHO_AM_I寄存器 u8 rx_buf[2]; spi_write_then_read(spi, tx_buf, 2, rx_buf, 2); } ``` --- #### 三、驱动编译与加载 1. **Makefile配置** ```makefile obj-m += spi_icm20608.o KDIR := /path/to/kernel/source all: make -C $(KDIR) M=$(PWD) modules ``` 2. **部署到开发板** ```bash # 将驱动模块拷贝到目标系统 sudo cp spi_icm20608.ko /lib/modules/$(uname -r)/kernel/drivers/spi/[^1] # 加载驱动模块 depmod -a modprobe spi_icm20608 ``` --- #### 四、测试与调试 1. **sysfs接口验证** ```bash # 查看SPI设备列表 ls /sys/bus/spi/devices/ # 读取寄存器值(需驱动实现sysfs接口) cat /sys/class/icm20608/icm20608/whoami ``` 2. **逻辑分析仪调试** 建议使用PulseView等工具抓取SCLK/MOSI/MISO波形,验证时序是否符合设备规格。 --- #### 五、参考资料 1. [Linux内核SPI子系统文档](https://www.kernel.org/doc/html/latest/driver-api/spi.html) [^2] 2. `spi.h`头文件中的API定义(包含spi_sync、spi_message等函数) ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值