imx6ul spi 设备驱动开发

spi设备树格式

spi设备在设备树里像描述i2c设备一样,需要在spi控制器节点里用子节点描述spi设备节点:
&spi0 { /* spi控制器节点 */
    ...
    cs-gpios = <&pio 2 3 GPIO_ACTIVE_HIGH>, <&pio 0 6 GPIO_ACTIVE_HIGH>;
        /* 片选的io口需与下面的spi设备节点一致  */

    spidev0 {
        compatible = "nanopi,spidev"; /* 此属性值用于与spi设备驱动匹配 */
        reg = <0>;  /*spi设备是没有设备地址的, 这里是指使用spi控制器的cs-gpios里的第几个片选io */
        status = "okay";  /* status属性值为"okay"表示spidev0设备使能, "disabled"表示设备没有使用*/
        spi-max-frequency = <10000000>; /* 指定spi设备的最大工作时钟 */

    ...
    buswidth = <8>; /* 传输以8位为单位 */
    mode = <0>;  /* 使用第几种工作时序(CPOL, CPHA) */
             /*但在现用的内核源码里发现, spi设备的工作时序并不是用mode属性值来指定的*/
             /* 如CPOL需要设1, 则只需在spi设备节点里加上"spi-cpol"属性即可; CPOL设0,则不写"spi-cpol"属性即可 */
             /* CPHA设1时, 则在设备节点里加上"spi-cpha"属性即可 */

    /*  还可以加入自定义的属性,用于指定工作时序方式及其它功能设置等 */
    }; 
};

spi设备树配置

&ecspi2 {
		compatible = "fsl,imx51-ecspi";
		fsl,spi-num-chipselects = <2>;
		cs-gpios = <&gpio4 22 0>,<&gpio3 20 0>;
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_ecspi2>,<&pinctrl_ecspi2_cs>;
		status = "okay";

		vs10xx-ctrl@0{
			compatible = "vs10xx-ctrl";
			cs-gpios = <&gpio4 22 0>;
			reg = <0>;//选择第一个片选引脚 MX6UL_PAD_CSI_DATA01__GPIO4_IO22
			// spi-cpol;
			// spi-cpha;spi-cpool和spi-cpha选择spi模式
			spi-max-frequency = <7000000>;
			status = "okay";
		};

		vs10xx-data@1{
			compatible = "vs10xx-data";
			cs-gpios = <&gpio3 20 0>;//spi片选
			reg = <1>;//选择第2个片选引脚 MX6UL_PAD_LCD_DATA15__GPIO3_IO20
			// spi-cpol;
			// spi-cpha; spi-cpool和spi-cpha选择spi模式
			spi-max-frequency = <9000000>;//spi最大频率
			status = "okay";
		};
};

pinctrl_ecspi2: ecspi2grp {
     fsl,pins = <
        MX6UL_PAD_CSI_DATA03__ECSPI2_MISO   0x100b1
        MX6UL_PAD_CSI_DATA02__ECSPI2_MOSI   0x100b1
        MX6UL_PAD_CSI_DATA00__ECSPI2_SCLK   0x100b1
     >;
};   
pinctrl_ecspi2_cs: ecspi2_csgrp {
    fsl,pins = <
        MX6UL_PAD_CSI_DATA01__GPIO4_IO22    0x80000000
        MX6UL_PAD_LCD_DATA15__GPIO3_IO20    0x80000000
    >;
};

spi 驱动 设备树解析

spi总线驱动路径

driver/spi/spi.c

spi 设备树属性解析:

/**
 * of_register_spi_devices() - Register child devices onto the SPI bus
 * @master:	Pointer to spi_master device
 *
 * Registers an spi_device for each child node of master node which has a 'reg'
 * property.
 */
static void of_register_spi_devices(struct spi_master *master)
{
	struct spi_device *spi;
	struct device_node *nc;
	int rc;
	u32 value;

	if (!master->dev.of_node)
		return;
	
	//遍历总线设备节点
	for_each_available_child_of_node(master->dev.of_node, nc) {
		/* Alloc an spi_device */
		spi = spi_alloc_device(master);
		if (!spi) {
			dev_err(&master->dev, "spi_device alloc error for %s\n",
				nc->full_name);
			spi_dev_put(spi);
			continue;
		}

		/* Select device driver */
		if (of_modalias_node(nc, spi->modalias,
				     sizeof(spi->modalias)) < 0) {
			dev_err(&master->dev, "cannot find modalias for %s\n",
				nc->full_name);
			spi_dev_put(spi);
			continue;
		}

		/* Device address */
		rc = of_property_read_u32(nc, "reg", &value);//读取reg属性
		if (rc) {
			dev_err(&master->dev, "%s has no valid 'reg' property (%d)\n",
				nc->full_name, rc);
			spi_dev_put(spi);
			continue;
		}
		spi->chip_select = value;

		/* Mode (clock phase/polarity/etc.) */
		if (of_find_property(nc, "spi-cpha", NULL))//根所给的名字查找对应的属性
			spi->mode |= SPI_CPHA;
		if (of_find_property(nc, "spi-cpol", NULL))
			spi->mode |= SPI_CPOL;
		if (of_find_property(nc, "spi-cs-high", NULL))
			spi->mode |= SPI_CS_HIGH;
		if (of_find_property(nc, "spi-3wire", NULL))
			spi->mode |= SPI_3WIRE;
		if (of_find_property(nc, "spi-lsb-first", NULL))
			spi->mode |= SPI_LSB_FIRST;

		/* Device DUAL/QUAD mode */
		if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) //读取spi-tx-bus-width属性值
		{
			switch (value) {
			case 1:
				break;
			case 2:
				spi->mode |= SPI_TX_DUAL;
				break;
			case 4:
				spi->mode |= SPI_TX_QUAD;
				break;
			default:
				dev_warn(&master->dev,
					 "spi-tx-bus-width %d not supported\n",
					 value);
				break;
			}
		}

		if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) //读取spi-rx-bus-width属性值
		{
			switch (value) {
			case 1:
				break;
			case 2:
				spi->mode |= SPI_RX_DUAL;
				break;
			case 4:
				spi->mode |= SPI_RX_QUAD;
				break;
			default:
				dev_warn(&master->dev,
					 "spi-rx-bus-width %d not supported\n",
					 value);
				break;
			}
		}

		/* Device speed */
		rc = of_property_read_u32(nc, "spi-max-frequency", &value);//读取spi最大传输速率
		if (rc) {
			dev_err(&master->dev, "%s has no valid 'spi-max-frequency' property (%d)\n",
				nc->full_name, rc);
			spi_dev_put(spi);
			continue;
		}
		spi->max_speed_hz = value;

		/* IRQ */
		spi->irq = irq_of_parse_and_map(nc, 0);

		/* Store a pointer to the node in the device structure */
		of_node_get(nc);
		spi->dev.of_node = nc;

		/* Register the new device */
		request_module("%s%s", SPI_MODULE_PREFIX, spi->modalias);
		rc = spi_add_device(spi);
		if (rc) {
			dev_err(&master->dev, "spi_device register error %s\n",
				nc->full_name);
			spi_dev_put(spi);
		}

	}
}

** of_find_property: **
通过所给的名字找到相应节点的属性
** of_property_read_u32 **
读取节点属性值

spi设备驱动使用

#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>
#include <linux/spi/spidev.h>
 
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
 
static void pabort(const char *s)
{
	perror(s);
	abort();
}
 
static const char *device = "/dev/spidev1.1";
static uint8_t mode;
static uint8_t bits = 8;
static uint32_t speed = 500000;
static uint16_t delay;
 
static void transfer(int fd)
{
	int ret;
	uint8_t tx[] = {	//要发送的数据数组
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
		0x40, 0x00, 0x00, 0x00, 0x00, 0x95,
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
		0xDE, 0xAD, 0xBE, 0xEF, 0xBA, 0xAD,
		0xF0, 0x0D,
	};
	uint8_t rx[ARRAY_SIZE(tx)] = {0, };	//接收的数据数据
	struct spi_ioc_transfer tr = {	//声明并初始化spi_ioc_transfer结构体
		.tx_buf = (unsigned long)tx,
		.rx_buf = (unsigned long)rx,
		.len = ARRAY_SIZE(tx),
		.delay_usecs = delay,
		.speed_hz = speed,
		.bits_per_word = bits,
	};
	//SPI_IOC_MESSAGE(1)的1表示spi_ioc_transfer的数量
	ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);	//ioctl默认操作,传输数据
	if (ret < 1)
		pabort("can't send spi message");
 
	for (ret = 0; ret < ARRAY_SIZE(tx); ret++) {	//打印接收缓冲区
		if (!(ret % 6))		//6个数据为一簇打印
			puts("");
		printf("%.2X ", rx[ret]);
	}
	puts("");
}
 
static void print_usage(const char *prog)	//参数错误则打印帮助信息
{
	printf("Usage: %s [-DsbdlHOLC3]\n", prog);
	puts("  -D --device   device to use (default /dev/spidev1.1)\n"
	     "  -s --speed    max speed (Hz)\n"
	     "  -d --delay    delay (usec)\n"
	     "  -b --bpw      bits per word \n"
	     "  -l --loop     loopback\n"
	     "  -H --cpha     clock phase\n"
	     "  -O --cpol     clock polarity\n"
	     "  -L --lsb      least significant bit first\n"
	     "  -C --cs-high  chip select active high\n"
	     "  -3 --3wire    SI/SO signals shared\n");
	exit(1);
}
 
static void parse_opts(int argc, char *argv[])
{
	while (1) {
		static const struct option lopts[] = {	//参数命令表
			{ "device",  1, 0, 'D' },
			{ "speed",   1, 0, 's' },
			{ "delay",   1, 0, 'd' },
			{ "bpw",     1, 0, 'b' },
			{ "loop",    0, 0, 'l' },
			{ "cpha",    0, 0, 'H' },
			{ "cpol",    0, 0, 'O' },
			{ "lsb",     0, 0, 'L' },
			{ "cs-high", 0, 0, 'C' },
			{ "3wire",   0, 0, '3' },
			{ "no-cs",   0, 0, 'N' },
			{ "ready",   0, 0, 'R' },
			{ NULL, 0, 0, 0 },
		};
		int c;
 
		c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR", lopts, NULL);
 
		if (c == -1)
			break;
 
		switch (c) {
		case 'D':	//设备名
			device = optarg;
			break;
		case 's':	//速率
			speed = atoi(optarg);
			break;
		case 'd':	//延时时间
			delay = atoi(optarg);
			break;
		case 'b':	//每字含多少位
			bits = atoi(optarg);
			break;
		case 'l':	//回送模式
			mode |= SPI_LOOP;
			break;
		case 'H':	//时钟相位
			mode |= SPI_CPHA;
			break;
		case 'O':	//时钟极性
			mode |= SPI_CPOL;
			break;
		case 'L':	//lsb 最低有效位
			mode |= SPI_LSB_FIRST;
			break;
		case 'C':	//片选高电平
			mode |= SPI_CS_HIGH;
			break;
		case '3':	//3线传输模式
			mode |= SPI_3WIRE;
			break;
		case 'N':	//没片选
			mode |= SPI_NO_CS;
			break;
		case 'R':	//从机拉低电平停止数据传输
			mode |= SPI_READY;
			break;
		default:	//错误的参数
			print_usage(argv[0]);
			break;
		}
	}
}
 
int main(int argc, char *argv[])
{
	int ret = 0;
	int fd;
 
	parse_opts(argc, argv);	//解析传递进来的参数
 
	fd = open(device, O_RDWR);	//打开设备文件
	if (fd < 0)
		pabort("can't open device");
 
	/*
	 * spi mode	//设置spi设备模式
	 */
	ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);	//写模式
	if (ret == -1)
		pabort("can't set spi mode");
 
	ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);	//读模式
	if (ret == -1)
		pabort("can't get spi mode");
 
	/*
	 * bits per word	//设置每个字含多少位
	 */
	ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);	//写 每个字含多少位
	if (ret == -1)
		pabort("can't set bits per word");
 
	ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);	//读 每个字含多少位
	if (ret == -1)
		pabort("can't get bits per word");
 
	/*
	 * max speed hz		//设置速率
	 */
	ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);	//写速率
	if (ret == -1)
		pabort("can't set max speed hz");
 
	ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);	//读速率
	if (ret == -1)
		pabort("can't get max speed hz");
	//打印模式,每字多少位和速率信息
	printf("spi mode: %d\n", mode);
	printf("bits per word: %d\n", bits);
	printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);
 
	transfer(fd);	//传输测试
 
	close(fd);	//关闭设备
 
	return ret;
}

这里整理下ioctl的命令:

SPI_IOC_RD_MODE		//读 模式
SPI_IOC_RD_LSB_FIRST	//读 LSB
SPI_IOC_RD_BITS_PER_WORD	//读 每字多少位
SPI_IOC_RD_MAX_SPEED_HZ	//读 最大速率
SPI_IOC_WR_MODE		//写 模式
SPI_IOC_WR_LSB_FIRST	//写 LSB
SPI_IOC_WR_BITS_PER_WORD	//写 每字多少位
SPI_IOC_WR_MAX_SPEED_HZ	//写 最大速率
SPI_IOC_MESSAGE(n)		//传输n个数据包

spi通用设备驱动

driver/spi/spidev.c

只需要在设备树中设置好spi属性,就可以启动此驱动文件。

spi测试工具

  1. spidev_test
  2. spi-tools

spi时序对比

spi api 接口

  1. spi_write

  2. spi_read

  3. spi_w8r16

  4. spi_w8r8

  5. spi_write_then_read

  6. spi_sync(同步),spi_async(异步)

  7. 上面的函数不可以同时收发数据,同时收发数据使用如下方法

        struct spi_message msg; //spi控制器的传输操作是以消息来提交. 一条消息里可有多个传输操作
        struct spi_transfer x = {  //每个spi_transfer对象来描述收、发、收发操作 
            .tx_buf = txbuf,  //指定发出数据的缓冲区地址
            .rx_buf = rxbuf,  //指定接收数据的缓冲区地址
            .len = strlen(txbuf),  //收发数据的长度
        };  
    
        spi_message_init(&msg);  //初始化消息对象
        spi_message_add_tail(&x, &msg); //把传输对象增加到消息对象里
    	spi_sync(spi, &msg); //提交消息对象到控制器
    
    

链接: spi通信接口分析

spi设备相关结构体

  1. spi_device
struct spi_device {
        struct device           dev; //基于device成员扩展, 在/sys/bus/spi/devie目录有相应的子目录(名为spi%d.%d)
        struct spi_master       *master; //spi控制器对象的地址
        u32                     max_speed_hz; //设备工作时钟最大多少HZ
        u8                      chip_select; 
        u8                      mode;    
#define SPI_CPHA        0x01                    /* clock phase */
#define SPI_CPOL        0x02                    /* clock polarity */
#define SPI_MODE_0      (0|0)                   /* (original MicroWire) */
#define SPI_MODE_1      (0|SPI_CPHA)
#define SPI_MODE_2      (SPI_CPOL|0)
#define SPI_MODE_3      (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH     0x04                    /* chipselect active high? */
#define SPI_LSB_FIRST   0x08                    /* per-word bits-on-wire */
#define SPI_3WIRE       0x10                    /* SI/SO signals shared */
#define SPI_LOOP        0x20                    /* loopback mode */
#define SPI_NO_CS       0x40                    /* 1 dev/bus, no chipselect */
#define SPI_READY       0x80                    /* slave pulls low to pause */
        u8                      bits_per_word; //传输的数据以多少位为单位
        int                     irq;    //中断号
        void                    *controller_state;
        void                    *controller_data; //给控制器的驱动使用 
	char                    modalias[SPI_NAME_SIZE]; //spi设备的名字
};

  1. spi_driver
// spi设备驱动类型
struct spi_driver {
    const struct spi_device_id *id_table;
    int         (*probe)(struct spi_device *spi);
    int         (*remove)(struct spi_device *spi);
    void            (*shutdown)(struct spi_device *spi);
    int         (*suspend)(struct spi_device *spi, pm_message_t mesg);
    int         (*resume)(struct spi_device *spi);
    struct device_driver    driver; //基于device_driver扩展, 驱动模型
};


extern int spi_register_driver(struct spi_driver *sdrv);
static inline void spi_unregister_driver(struct spi_driver *sdrv);

spi 总线相关结构

spi总线:
struct bus_type spi_bus_type = {
    .name       = "spi",
    .dev_attrs  = spi_dev_attrs,
    .match      = spi_match_device,
    .uevent     = spi_uevent,
    .pm     = &spi_pm,
};
EXPORT_SYMBOL_GPL(spi_bus_type);

static int spi_match_device(struct device *dev, struct device_driver *drv)
{
    const struct spi_device *spi = to_spi_device(dev);
    const struct spi_driver *sdrv = to_spi_driver(drv);
	...
    if (sdrv->id_table)
        return !!spi_match_id(sdrv->id_table, spi); //如设备驱动有id_table则按id_table里指定的设备名来匹配 

    return strcmp(spi->modalias, drv->name) == 0; //如设备驱动没有id_table则按名字来匹配 
}


申明spi设备方法

1.设备树
2.在script.bin里声明spi设备. 这种方法只适用于SOC里spi控制器, 不适用基于GPIO口的控制器

[spi_devices]
spi_dev_num = 1   //表示spi设备的个数
  
[spi_board0]  //第0个spi设备
modalias = "spidev"  //设备名
max_speed_hz = 33000000 //设备最高的工作时钟
bus_num = 0            //此设备是连接在编号为0的控制器上的
chip_select = 0        //使用控制器的第0个片选 
mode = 0               //工作时序的方式
full_duplex = 1        //全双工, 表示MISO,MOSI都用上
manual_cs = 0          //表示片选线是由控制器自动控制的

3.在内核源码声明spi_board_info对象, 再内核初始化函数里spi_register_board_info注册设备信息

struct spi_board_info myspi_info = {
        .modalias = "spidev",
        .controller_data = GPIOA(20), //注意这里是指定设备的片选脚, 需查看控制器驱动代码里对controller_data成员的使用。
        .max_speed_hz = 100000, //100Khz
        .bus_num = 0,           //控制器的编号
        .chip_select = 0,      //第0个片选
        .mode = SPI_MODE_2,
};

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值