参考资料
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.88all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o dac_test dac_test.cclean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.order dac_testobj-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函数中实现这个传输函数。