设备树中的spi子系统

  • Post author:
  • Post category:其他


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;
}

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



版权声明:本文为qq_18737805原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。