当前位置: 首页 > article >正文

Linux中SPI

参考资料

https://www.cnblogs.com/aaronLinux/p/6219146.html

1.SPI
在这里插入图片描述
2.SPI传输
2.1传输示例

首先,CS0拉低选中的SPI Flash ,
然后在每个时钟周期,
DO输出对应的电平。
SPI FLASH会在每个时钟的上升沿读取D0的电平。

在这里插入图片描述
2.2SPI模式
根据SCK的电平以及数据在第一个跳变沿还是第二个跳变沿传输,SPI传输总共有四种模式。
在这里插入图片描述
在这里插入图片描述
3.SPI总线设备驱动模型

SPI系统中设涉及两类硬件:
SPI控制器
SPI设备

在这里插入图片描述
spi控制器有驱动程序,提供spi的传输能力。
spi设备也有自己的驱动程序,提供spi设备的访问能力。
4.spi设备驱动框架图

SPI驱动程序由spi_master及spi_device组成
spi_master是在设备树中定义的spi master及master
下边的device信息;当左边drive中的of_device_id和设备树中的compatible参数匹配的时候,会调用driver中的prboe函数,probe函数会生成master,还会生成spi_device。
spi设备左边包括一个spi驱动,当其中的of_device_id和spi_device匹配时,会调用左边driver中的probe函数。


在这里插入图片描述
4.1SPI控制器驱动程序
基于平台总线设备驱动模型实现。在probe函数中除了生成spi_master,还会创建spi_device结构体。
在这里插入图片描述
4.2SPI设备驱动程序

左边是spi_drive,里边有id_table表示能支持哪些SPI设备,有probe函数。
右边是spi_device,用来描述spi设备,可以来自设备树或者c文件

在这里插入图片描述

5.spi设备树处理过程
5.1spi_device

/**
 * struct spi_device - Master side proxy for an SPI slave device
 * @dev: Driver model representation of the device.
 * @master: SPI controller used with the device.
 * @max_speed_hz: Maximum clock rate to be used with this chip
 *	(on this board); may be changed by the device's driver.
 *	The spi_transfer.speed_hz can override this for each transfer.
 * @chip_select: Chipselect, distinguishing chips handled by @master.
 * @mode: The spi mode defines how data is clocked out and in.
 *	This may be changed by the device's driver.
 *	The "active low" default for chipselect mode can be overridden
 *	(by specifying SPI_CS_HIGH) as can the "MSB first" default for
 *	each word in a transfer (by specifying SPI_LSB_FIRST).
 * @bits_per_word: Data transfers involve one or more words; word sizes
 *	like eight or 12 bits are common.  In-memory wordsizes are
 *	powers of two bytes (e.g. 20 bit samples use 32 bits).
 *	This may be changed by the device's driver, or left at the
 *	default (0) indicating protocol words are eight bit bytes.
 *	The spi_transfer.bits_per_word can override this for each transfer.
 * @irq: Negative, or the number passed to request_irq() to receive
 *	interrupts from this device.
 * @controller_state: Controller's runtime state
 * @controller_data: Board-specific definitions for controller, such as
 *	FIFO initialization parameters; from board_info.controller_data
 * @modalias: Name of the driver to use with this device, or an alias
 *	for that name.  This appears in the sysfs "modalias" attribute
 *	for driver coldplugging, and in uevents used for hotplugging
 * @cs_gpio: gpio number of the chipselect line (optional, -ENOENT when
 *	when not using a GPIO line)
 *
 * @statistics: statistics for the spi_device
 *
 * A @spi_device is used to interchange data between an SPI slave
 * (usually a discrete chip) and CPU memory.
 *
 * In @dev, the platform_data is used to hold information about this
 * device that's meaningful to the device's protocol driver, but not
 * to its controller.  One example might be an identifier for a chip
 * variant with slightly different functionality; another might be
 * information about how this particular board wires the chip's pins.
 */
struct spi_device {
	struct device		dev;
	struct spi_master	*master;
	u32			max_speed_hz;/* 该设备能支持的spi时钟最大值 */
	u8			chip_select;/* 是对应的spi_master下边的第几个设备 */
	u8			bits_per_word;/* 每个基本的spi传输涉及多少位 */
	u16			mode; /*spi_chpa  spi_cpol组合起来得到spi传输的四种模式 */
#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 */
#define	SPI_TX_DUAL	0x100			/* transmit with 2 wires */
#define	SPI_TX_QUAD	0x200			/* transmit with 4 wires */
#define	SPI_RX_DUAL	0x400			/* receive with 2 wires */
#define	SPI_RX_QUAD	0x800			/* receive with 4 wires */
	int			irq;
	void			*controller_state;
	void			*controller_data;
	char			modalias[SPI_NAME_SIZE];
	int			cs_gpio;	/* chip select gpio */

	/* the statistics */
	struct spi_statistics	statistics;

	/*
	 * likely need more hooks for more protocol options affecting how
	 * the controller talks to each chip, like:
	 *  - memory packing (12 bit samples into low bits, others zeroed)
	 *  - priority
	 *  - drop chipselect after each word
	 *  - chipselect delays
	 *  - ...
	 */
};

5.2设备树中的spi节点

    spi4 {
        compatible = "spi-gpio";/* 这个属性很关键,因为根据这个属性找到spi_master */
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_spi4>;
        pinctrl-assert-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;
        status = "okay";
        gpio-sck = <&gpio5 11 0>;
        gpio-mosi = <&gpio5 10 0>;
        cs-gpios = <&gpio5 7 0>;
        num-chipselects = <1>;
        #address-cells = <1>;/* 这个SPI Master下的SPI设备,需要多少个cell来表述它的片选引脚 */
        #size-cells = <0>; /* 这个必须设置为0 */
		/* gpio_spi是spi_master 下边的device节点 */
        gpio_spi: gpio_spi@0 {
            compatible = "fairchild,74hc595";/* 根据它找到spi device驱动 */
            gpio-controller;
            #gpio-cells = <2>;
            reg = <0>;/* 使用哪个片选引脚 */
            registers-number = <1>;
            registers-default = /bits/ 8 <0x57>;
            spi-max-frequency = <10000>;/* 该设备支持的最大spi时钟 */
        };
    };

在这里插入图片描述

6.spi device设备驱动程序编写
6.1怎么编写SPI设备驱动程序

1.查看原理图,确定设备在哪个spi控制器下边。
2.在设备树中找到对应的spi控制器,在该节点下创建子节点,表示spi device。

在这里插入图片描述
6.2注册spi driver
spi设备树中的节点,会被转换为一个spi device结构体。
需要编写一个spi driver来支持它。
参考spidev.c来编写设备驱动程序。
基于spi bus模型来编写驱动程序。
在这里插入图片描述
6.3怎么发起传输
6.3.1在函数spi.h中

Linux-4.9.88\include\linux\spi\spi.h

/**
 * spi_write - SPI synchronous write
 * @spi: device to which data will be written
 * @buf: data buffer
 * @len: data buffer size
 * Context: can sleep
 *
 * This function writes the buffer @buf.
 * Callable only from contexts that can sleep.
 *
 * Return: zero on success, else a negative error code.
 */
static inline int
spi_write(struct spi_device *spi, const void *buf, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= buf,
			.len		= len,
		};

	return spi_sync_transfer(spi, &t, 1);
}


/**
 * spi_read - SPI synchronous read
 * @spi: device from which data will be read
 * @buf: data buffer
 * @len: data buffer size
 * Context: can sleep
 *
 * This function reads the buffer @buf.
 * Callable only from contexts that can sleep.
 *
 * Return: zero on success, else a negative error code.
 */
static inline int
spi_read(struct spi_device *spi, void *buf, size_t len)
{
	struct spi_transfer	t = {
			.rx_buf		= buf,
			.len		= len,
		};

	return spi_sync_transfer(spi, &t, 1);
}


/* this copies txbuf and rxbuf data; for small transfers only! */
extern int spi_write_then_read(struct spi_device *spi,
		const void *txbuf, unsigned n_tx,
		void *rxbuf, unsigned n_rx);


/**
 * spi_w8r8 - SPI synchronous 8 bit write followed by 8 bit read
 * @spi: device with which data will be exchanged
 * @cmd: command to be written before data is read back
 * Context: can sleep
 *
 * Callable only from contexts that can sleep.
 *
 * Return: the (unsigned) eight bit number returned by the
 * device, or else a negative error code.
 */
static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd)
{
	ssize_t			status;
	u8			result;

	status = spi_write_then_read(spi, &cmd, 1, &result, 1);

	/* return negative errno or unsigned value */
	return (status < 0) ? status : result;
}


/**
 * spi_w8r16 - SPI synchronous 8 bit write followed by 16 bit read
 * @spi: device with which data will be exchanged
 * @cmd: command to be written before data is read back
 * Context: can sleep
 *
 * The number is returned in wire-order, which is at least sometimes
 * big-endian.
 *
 * Callable only from contexts that can sleep.
 *
 * Return: the (unsigned) sixteen bit number returned by the
 * device, or else a negative error code.
 */
static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd)
{
	ssize_t			status;
	u16			result;

	status = spi_write_then_read(spi, &cmd, 1, &result, 2);

	/* return negative errno or unsigned value */
	return (status < 0) ? status : result;
}


/**
 * spi_w8r16be - SPI synchronous 8 bit write followed by 16 bit big-endian read
 * @spi: device with which data will be exchanged
 * @cmd: command to be written before data is read back
 * Context: can sleep
 *
 * This function is similar to spi_w8r16, with the exception that it will
 * convert the read 16 bit data word from big-endian to native endianness.
 *
 * Callable only from contexts that can sleep.
 *
 * Return: the (unsigned) sixteen bit number returned by the device in cpu
 * endianness, or else a negative error code.
 */
static inline ssize_t spi_w8r16be(struct spi_device *spi, u8 cmd)

{
	ssize_t status;
	__be16 result;

	status = spi_write_then_read(spi, &cmd, 1, &result, 2);
	if (status < 0)
		return status;

	return be16_to_cpu(result);
}

6.3.2函数解析
在spi子系统中,用spi_transfer结构体描述一个传输,用spi_message管理整个传输。
在这里插入图片描述
spi_transfer结构体:
在这里插入图片描述
spi_message结构体在这里插入图片描述
spi传输示例 在这里插入图片描述
7.编写spi DAC模块驱动程序
7.1基于spi master spi device设备驱动模型来编写设备的驱动程序。

1.查看原理图,看spi device在哪个spi master下边,
然后再设备树中对应的master下边添加设备节点。
2.编写驱动程序,注册一个spi device。
3.编写测试程序。

1.将DAC模块连接到SPI_A上边
在这里插入图片描述
2.在设备树中创建子节点
在这里插入图片描述
3.编写spi_device设备驱动程序


#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>

#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>

#include <linux/uaccess.h>

#define SPI_IOC_WR 123

/*-------------------------------------------------------------------------*/

static struct spi_device *dac;
static int major;

static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int val;
	int err;
	unsigned char tx_buf[2];	
	unsigned char rx_buf[2];	

	struct spi_message	msg;
	struct spi_transfer	xfer[1];
	int status;

	memset(&xfer[0], 0, sizeof(xfer));
	
	/* copy_from_user */
	err = copy_from_user(&val, (const void __user *)arg, sizeof(int));

	printk("spidev_ioctl get val from user: %d\n", val);

	/* 发起SPI传输:     */

	/* 1. 把val修改为正确的格式 */
	val <<= 2;     /* bit0,bit1 = 0b00 */
	val &= 0xFFC;  /* 只保留10bit */

	tx_buf[1] = val & 0xff;
	tx_buf[0] = (val>>8) & 0xff;	

	/* 2. 发起SPI传输同时写\读 */
	/* 2.1 构造transfer
	 * 2.2 加入message
	 * 2.3 调用spi_sync
	 */
	xfer[0].tx_buf = tx_buf;
	xfer[0].rx_buf = rx_buf;
	xfer[0].len = 2;

	spi_message_init(&msg);
	spi_message_add_tail(&xfer[0], &msg);
	
	status = spi_sync(dac, &msg);

	/* 3. 修改读到的数据的格式 */
	val = (rx_buf[0] << 8) | (rx_buf[1]);
	val >>= 2;

	printk("spidev_ioctl get val to user: %d\n", val);

	/* copy_to_user */
	err = copy_to_user((void __user *)arg, &val, sizeof(int));
	
	return 0;
}


static const struct file_operations spidev_fops = {
	.owner =	THIS_MODULE,
	/* REVISIT switch to aio primitives, so that userspace
	 * gets more complete API coverage.  It'll simplify things
	 * too, except for the locking.
	 */
	.unlocked_ioctl = spidev_ioctl,
};



static struct class *spidev_class;

static const struct of_device_id spidev_dt_ids[] = {
	{ .compatible = "100ask,dac" },
	{},
};


/*-------------------------------------------------------------------------*/

static int spidev_probe(struct spi_device *spi)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 1. 记录spi_device */
	dac = spi;

	/* 2. 注册字符设备 */
	major = register_chrdev(0, "100ask_dac", &spidev_fops);
	spidev_class = class_create(THIS_MODULE, "100ask_dac");
	device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_dac");	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	return 0;
}

static int spidev_remove(struct spi_device *spi)
{
	/* 反注册字符设备 */
	device_destroy(spidev_class, MKDEV(major, 0));
	class_destroy(spidev_class);
	unregister_chrdev(major, "100ask_dac");
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	return 0;
}

static struct spi_driver spidev_spi_driver = {
	.driver = {
		.name =		"100ask,dac",
		.of_match_table = of_match_ptr(spidev_dt_ids),
	},
	.probe =	spidev_probe,
	.remove =	spidev_remove,
};

/*-------------------------------------------------------------------------*/

static int __init spidev_init(void)
{
	int status;

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	status = spi_register_driver(&spidev_spi_driver);
	if (status < 0) {
	}
	return status;
}
module_init(spidev_init);

static void __exit spidev_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	spi_unregister_driver(&spidev_spi_driver);
}
module_exit(spidev_exit);

MODULE_LICENSE("GPL");


4.测试程序



/* 参考: tools\spi\spidev_fdx.c */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <linux/types.h>

#define SPI_IOC_WR 123

/* dac_test /dev/100ask_dac <val> */

int main(int argc, char **argv)
{
	int fd;
	unsigned int val;
	int status;

	unsigned char tx_buf[2];	
	unsigned char rx_buf[2];	
	
	if (argc != 3)
	{
		printf("Usage: %s /dev/100ask_dac <val>\n", argv[0]);
		return 0;
	}

	fd = open(argv[1], O_RDWR);
	if (fd < 0) {
		printf("can not open %s\n", argv[1]);
		return 1;
	}

	val = strtoul(argv[2], NULL, 0);

	status = ioctl(fd, SPI_IOC_WR, &val);
	if (status < 0) {
		printf("SPI_IOC_WR\n");
		return -1;
	}

	/* 打印 */
	printf("Pre val = %d\n", val);
	
	
	return 0;
}


5.Makefile

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o dac_test dac_test.c

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

obj-m	+= dac_drv.o

8.spi master的实现

1.修改设备树
2.编写驱动
	分配、设置、注册spi_master

8.1spi传输概述
使用spi传输时,最小的传输单位是spi_transfer
一个设备,可以传输多个spi_transfer, 这些spi_tranfer会放在一个spi_message中。
spi_master中传输函数
在这里插入图片描述
代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/spi/spi.h>

static struct spi_master *g_virtual_master;
static struct work_struct g_virtual_ws;

static const struct of_device_id spi_virtual_dt_ids[] = {
	{ .compatible = "100ask,virtual_spi_master", },
	{ /* sentinel */ }
};


static void spi_virtual_work(struct work_struct *work)
{
	struct spi_message *mesg;
	
	while (!list_empty(&g_virtual_master->queue)) {
		mesg = list_entry(g_virtual_master->queue.next, struct spi_message, queue);
		list_del_init(&mesg->queue);
		
		/* 假装硬件传输已经完成 */

		mesg->status = 0;
		if (mesg->complete)
			mesg->complete(mesg->context);

	}	
}

static int spi_virtual_transfer(struct spi_device *spi, struct spi_message *mesg)
{
#if 0	
	/* 方法1: 直接实现spi传输 */
	/* 假装传输完成, 直接唤醒 */
	mesg->status = 0;
	mesg->complete(mesg->context);
	return 0;
	
#else
	/* 方法2: 使用工作队列启动SPI传输、等待完成 */
	/* 把消息放入队列 */
	mesg->actual_length = 0;
	mesg->status = -EINPROGRESS;
	list_add_tail(&mesg->queue, &spi->master->queue);
	
	/* 启动工作队列 */
	schedule_work(&g_virtual_ws);
	
	/* 直接返回 */
	return 0;
#endif	
}


static int spi_virtual_probe(struct platform_device *pdev)
{
	struct spi_master *master;
	int ret;
	
	/* 分配/设置/注册spi_master */
	g_virtual_master = master = spi_alloc_master(&pdev->dev, 0);
	if (master == NULL) {
		dev_err(&pdev->dev, "spi_alloc_master error.\n");
		return -ENOMEM;
	}

	master->transfer = spi_virtual_transfer;
	INIT_WORK(&g_virtual_ws, spi_virtual_work);

	master->dev.of_node = pdev->dev.of_node;
	ret = spi_register_master(master);
	if (ret < 0) {
		printk(KERN_ERR "spi_register_master error.\n");
		spi_master_put(master);
		return ret;
	}

	return 0;

	
}

static int spi_virtual_remove(struct platform_device *pdev)
{
	/* 反注册spi_master */
	spi_unregister_master(g_virtual_master);
	return 0;
}


static struct platform_driver spi_virtual_driver = {
	.probe = spi_virtual_probe,
	.remove = spi_virtual_remove,
	.driver = {
		.name = "virtual_spi",
		.of_match_table = spi_virtual_dt_ids,
	},
};

static int virtual_master_init(void)
{
	return platform_driver_register(&spi_virtual_driver);
}

static void virtual_master_exit(void)
{
	platform_driver_unregister(&spi_virtual_driver);
}

module_init(virtual_master_init);
module_exit(virtual_master_exit);

MODULE_DESCRIPTION("Virtual SPI bus driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.100ask.net");


在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
之后,再在master函数中实现这个传输函数。


http://www.kler.cn/a/377800.html

相关文章:

  • vue2项目使用360安全浏览器默认采用极速模式打开运行
  • uniapp h5端临时路径转file对象上传
  • 反步法设计控制器(原理、步骤、适用系统)
  • 【北京迅为】《STM32MP157开发板嵌入式开发指南》-第七十二章 Debian文件系统
  • leetcode-有效的字母异位词
  • JAVA 插入 JSON 对象到 PostgreSQL
  • 重学SpringBoot3-整合 Elasticsearch 8.x (一)客户端方式
  • 使用 Logback 的最佳实践:`logback.xml` 与 `logback-spring.xml` 的区别与用法
  • 力扣题解(大礼包)
  • yarn 下载安装、下载依赖、通过 vscode 运行服务(Windows11)
  • 对于自带缓存的对象的注意点
  • 8. 数据结构——邻接表、邻接矩阵的基本操作
  • Elasticsearch Search Template 搜索模板
  • 代码随想录算法训练营第十五天| 654.最大二叉树 、617.合并二叉树 、700.二叉搜索树中的搜索、98.验证二叉搜索树
  • AcWing 320 能量项链 状态压缩dp
  • 【C++刷题】力扣-#566-重塑矩阵
  • 前端八股文第四篇
  • WorkFlow源码剖析——Communicator之TCPServer(上)
  • Linux:编辑器Vim和Makefile
  • ResTful风格的Url
  • Mac如何实现高效且干净的卸载应用程序
  • Gateway解说
  • 目标追踪DeepSort
  • network HCIE认证
  • 一文带你深入理解Rust 中的 Trait 一致性(Coherence)
  • SparkSQL整合Hive后,如何启动hiveserver2服务