赞
踩
目录
1、硬件原理:
SPI接口共有4根信号线,分别是:设备选择线(CS)、时钟线(SCLK)、串行输出数据线(MOSI)、串行输入数据线(MISO);spi控制器可以同时连接多个spi设备,但同一时刻只能有一个 SPI 设备处于工作状态,即多个 CS 信号中某时间只能有一个有效。
2、通讯方式
通过 CS 片选信号使能外部 SPI 设备后,SCLK 同步数据传输。MOSI 和 MISO 信号在 SCLK 上升沿变化,在下降沿锁存数据。
3.工作模式
spi有四种工作模式,具体由CPOL,CPHA决定;
CPOL:时钟极性,SCLK信号初始电平;CPHA:数据是在第一个上升沿采集数据还是第二个上升沿采集数据;
CPOL | SCLK | 模式 | 含义 |
0 | 0 | 1 | 初始电平为低电平,在第一个时钟沿采样数据 |
0 | 1 | 2 | 初始电平为低电平,在第二个时钟沿采样数据 |
1 | 0 | 3 | 初始电平为高电平,在第一个时钟沿采样数据 |
1 | 1 | 4 | 初始电平为高电平,在第二个时钟沿采样数据 |
常用的是模式 0 和模式 3,因为它们都是 在上升沿采样数据,不用去在乎时钟的初始电平是什么,只要在上升沿采集数据 就行。
spi驱动框架大致分为spi核心层,spi控制器驱动和spi设备驱动。
spi核心层向控制器驱动层提供注册SPI控制器驱动的接口,并提供一些需要控制器驱动实现的回调函数。核心层向上,对SPI设备驱动,提供标准的SPI收发API,以及设备注册函数。
spi控制器驱动会根据设备树里面描述的spi控制器的硬件信息注册一个spi_master,还会解析设备树中该spi控制器的子节点,创建spi_device结构体。驱动实现在Linux/drivers/spi/spi.c。
spi设备驱动框架主要是注册具体设备,实现硬件相关的读写接口,参考内核代码Linuc/drivers/spi/spidev.c 。
在设备树里,使用一个节点来表示SPI Master,使用子节点来表示挂在下面的SPI设备。设备树节点参考如下代码:
- spi {
- /*spi控制器*/
- #address-cells = <1>;
- #size-cells = <0>;
- compatible = "fsl,spi";
- reg = <0xf00 0x20>;
- interrupts = <2 13 0 2 14 0>;
- interrupt-parent = <&mpc5200_pic>;
-
- /*spi设备节点1*/
- ethernet-switch@0 {
- compatible = "micrel,ks8995m";
- spi-max-frequency = <1000000>;
- reg = <0>;
- };
-
- /*spi设备节点2*/
- codec@1 {
- compatible = "ti,tlv320aic26";
- spi-max-frequency = <100000>;
- reg = <1>;
- };
- };

对于spi master,必须的属性如下:
可选的属性如下:
对于spi设备,必选的属性如下:
可选的属性如下:
先从入口函数看起,在入口函数中主要注册了一个字符设备spidev_fops,和spidev_spi_driver;
spidev.c是一个通用的spi设备驱动程序,设备树某个spi节点下的子设备的compatible属性为下边spidev_dt_ids的某个,就会跟spidev匹配,既然是通用的spi驱动,那么肯定不止支持下面的几个,根据匹配关系发现,只要是设备树的compatible属性为"spidev",就能跟spidev匹配成功。
匹配之后,spidev.c的spidev_probe会被调用,在probe里面首先分配一个spidev_data结构体,用来记录对应的spi_device, spidev_data会被记录在一个链表里;分配一个次设备号,以后可以根据这个次设备号在链表里找到spidev_data;生成一个设备节点 /dev/spidevB.D ,B表示总线号,D表示它是这个SPI Master下第几个设备,以后,我们就可以通过/dev/spidevB.D来访问spidev驱动程序。
- static int spidev_probe(struct spi_device *spi)
- {
- struct spidev_data *spidev;
- int status;
- unsigned long minor;
-
- /*
- * spidev should never be referenced in DT without a specific
- * compatible string, it is a Linux implementation thing
- * rather than a description of the hardware.
- */
- WARN(spi->dev.of_node &&
- of_device_is_compatible(spi->dev.of_node, "spidev"),
- "%pOF: buggy DT: spidev listed directly in DT\n", spi->dev.of_node);
-
- spidev_probe_acpi(spi);
-
- /* Allocate driver data */
- spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);/*给spidev申请私有内存空间*/
- if (!spidev)
- return -ENOMEM;
-
- /* Initialize the driver data */
- spidev->spi = spi; /*记录spi*/
- spin_lock_init(&spidev->spi_lock);
- mutex_init(&spidev->buf_lock);
-
- INIT_LIST_HEAD(&spidev->device_entry);
-
- /* If we can allocate a minor number, hook up this device.
- * Reusing minors is fine so long as udev or mdev is working.
- */
- mutex_lock(&device_list_lock);
- minor = find_first_zero_bit(minors, N_SPI_MINORS);
- if (minor < N_SPI_MINORS) {
- struct device *dev;
-
- spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
-
- /*生成一个设备节点 /dev/spidevB.D */
- dev = device_create(spidev_class, &spi->dev, spidev->devt,
- spidev, "spidev%d.%d",
- spi->master->bus_num, spi->chip_select);
- status = PTR_ERR_OR_ZERO(dev);
- } else {
- dev_dbg(&spi->dev, "no minor number available!\n");
- status = -ENODEV;
- }
- if (status == 0) {
- set_bit(minor, minors);
- list_add(&spidev->device_entry, &device_list);/*把spidev_data放入链表*/
- }
- mutex_unlock(&device_list_lock);
-
- spidev->speed_hz = spi->max_speed_hz;/*记录时钟频率*/
-
- if (status == 0)
- spi_set_drvdata(spi, spidev);
- else
- kfree(spidev);
-
- return status;
- }

然后看看spidev_fops,它提供了open,read,write,ioctl等读写API。
spidev_read函数
- /* Read-only message with current device setup */
- static ssize_t
- spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
- {
- struct spidev_data *spidev;
- ssize_t status = 0;
-
- /* chipselect only toggles at start or end of operation */
- if (count > bufsiz)
- return -EMSGSIZE;
-
- spidev = filp->private_data;
-
- mutex_lock(&spidev->buf_lock);
- status = spidev_sync_read(spidev, count);
- if (status > 0) {
- unsigned long missing;
-
- missing = copy_to_user(buf, spidev->rx_buffer, status);
- if (missing == status)
- status = -EFAULT;
- else
- status = status - missing;
- }
- mutex_unlock(&spidev->buf_lock);
-
- return status;
- }
-
- static inline ssize_t
- spidev_sync_read(struct spidev_data *spidev, size_t len)
- {
- struct spi_transfer t = {
- .rx_buf = spidev->rx_buffer,
- .len = len,
- .speed_hz = spidev->speed_hz,
- };
- struct spi_message m;
-
- spi_message_init(&m);
- spi_message_add_tail(&t, &m);
- return spidev_sync(spidev, &m);
- }
-
- /*-------------------------------------------------------------------------*/
- spidev_sync(struct spidev_data *spidev, struct spi_message *message)
- {
- int status;
- struct spi_device *spi;
-
- spin_lock_irq(&spidev->spi_lock);
- spi = spidev->spi;
- spin_unlock_irq(&spidev->spi_lock);
-
- if (spi == NULL)
- status = -ESHUTDOWN;
- else
- status = spi_sync(spi, message);
-
- if (status == 0)
- status = message->actual_length;
-
- return status;
- }
-
-

在spidev_read函数中,调用 spidev_sync_read,在spidev_sync_read中定义spi_transfer和spi_message,然后通过spidev_sync发送,spidev_sync最终会调用内核提供的spi_sync来实现读数据,得到数据后使用copy_to_user把内核空间的数据发给应用程序;
spidev_write函数
- static ssize_t
- spidev_write(struct file *filp, const char __user *buf,
- size_t count, loff_t *f_pos)
- {
- struct spidev_data *spidev;
- ssize_t status = 0;
- unsigned long missing;
-
- if (count > bufsiz)
- return -EMSGSIZE;
- spidev = filp->private_data;
-
- mutex_lock(&spidev->buf_lock);
- missing = copy_from_user(spidev->tx_buffer, buf, count);
- if (missing == 0)
- status = spidev_sync_write(spidev, count);
- status = -EFAULT;
- mutex_unlock(&spidev->buf_lock);
-
- return status;
- }
-
- static inline ssize_t
- spidev_sync_write(struct spidev_data *spidev, size_t len)
- {
- struct spi_transfer t = {
- .tx_buf = spidev->tx_buffer,
- .len = len,
- .speed_hz = spidev->speed_hz,
- };
- struct spi_message m;
-
- spi_message_init(&m);
- spi_message_add_tail(&t, &m);
- return spidev_sync(spidev, &m);
- }
-
- static ssize_t
- spidev_sync(struct spidev_data *spidev, struct spi_message *message)
- {
- int status;
- struct spi_device *spi;
- spin_lock_irq(&spidev->spi_lock);
- spi = spidev->spi;
- spin_unlock_irq(&spidev->spi_lock);
- if (spi == NULL)
- status = -ESHUTDOWN;
- else
- status = spi_sync(spi, message);
- if (status == 0)
- status = message->actual_length;
- return status;
- }
-

在spi_write函数中,首先要把写的数据拷贝到内核空间,然后把数据传到spi_sync_write函数,在spi_sync_write定义spi_transfer和spi_message,然后通过spidev_sync发送,spidev_sync最终会调用内核提供的spi_sync来实现写数据。
spi_write和spi_read只能读、写,这是半双工方式,要实现全双工模式的话要使用spi_ioctl。
spi_ioctl函数
- spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
- {
- int retval = 0;
- struct spidev_data *spidev;
- struct spi_device *spi;
- u32 tmp;
- unsigned n_ioc;
- struct spi_ioc_transfer *ioc;
-
- /* Check type and command number */
- if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)
- return -ENOTTY;
-
- /* guard against device removal before, or while,
- * we issue this ioctl.
- */
- spidev = filp->private_data;
- spin_lock_irq(&spidev->spi_lock);
- spi = spi_dev_get(spidev->spi);
- spin_unlock_irq(&spidev->spi_lock);
-
- if (spi == NULL)
- return -ESHUTDOWN;
-
- /* use the buffer lock here for triple duty:
- * - prevent I/O (from us) so calling spi_setup() is safe;
- * - prevent concurrent SPI_IOC_WR_* from morphing
- * data fields while SPI_IOC_RD_* reads them;
- * - SPI_IOC_MESSAGE needs the buffer locked "normally".
- */
- mutex_lock(&spidev->buf_lock);
-
- switch (cmd) {
- /* read requests */
- case SPI_IOC_RD_MODE:
- retval = put_user(spi->mode & SPI_MODE_MASK,
- (__u8 __user *)arg);
- break;
- case SPI_IOC_RD_MODE32:
- retval = put_user(spi->mode & SPI_MODE_MASK,
- (__u32 __user *)arg);
- break;
- case SPI_IOC_RD_LSB_FIRST:
- retval = put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0,
- (__u8 __user *)arg);
- break;
- case SPI_IOC_RD_BITS_PER_WORD:
- retval = put_user(spi->bits_per_word, (__u8 __user *)arg);
- break;
- case SPI_IOC_RD_MAX_SPEED_HZ:
- retval = put_user(spidev->speed_hz, (__u32 __user *)arg);
- break;
-
- /* write requests */
- case SPI_IOC_WR_MODE:
- case SPI_IOC_WR_MODE32:
- if (cmd == SPI_IOC_WR_MODE)
- retval = get_user(tmp, (u8 __user *)arg);
- else
- retval = get_user(tmp, (u32 __user *)arg);
- if (retval == 0) {
- struct spi_controller *ctlr = spi->controller;
- u32 save = spi->mode;
-
- if (tmp & ~SPI_MODE_MASK) {
- retval = -EINVAL;
- break;
- }
-
- if (ctlr->use_gpio_descriptors && ctlr->cs_gpiods &&
- ctlr->cs_gpiods[spi->chip_select])
- tmp |= SPI_CS_HIGH;
-
- tmp |= spi->mode & ~SPI_MODE_MASK;
- spi->mode = (u16)tmp;
- retval = spi_setup(spi);
- if (retval < 0)
- spi->mode = save;
- else
- dev_dbg(&spi->dev, "spi mode %x\n", tmp);
- }
- break;
- case SPI_IOC_WR_LSB_FIRST:
- retval = get_user(tmp, (__u8 __user *)arg);
- if (retval == 0) {
- u32 save = spi->mode;
-
- if (tmp)
- spi->mode |= SPI_LSB_FIRST;
- else
- spi->mode &= ~SPI_LSB_FIRST;
- retval = spi_setup(spi);
- if (retval < 0)
- spi->mode = save;
- else
- dev_dbg(&spi->dev, "%csb first\n",
- tmp ? 'l' : 'm');
- }
- break;
- case SPI_IOC_WR_BITS_PER_WORD:
- retval = get_user(tmp, (__u8 __user *)arg);
- if (retval == 0) {
- u8 save = spi->bits_per_word;
-
- spi->bits_per_word = tmp;
- retval = spi_setup(spi);
- if (retval < 0)
- spi->bits_per_word = save;
- else
- dev_dbg(&spi->dev, "%d bits per word\n", tmp);
- }
- break;
- case SPI_IOC_WR_MAX_SPEED_HZ:
- retval = get_user(tmp, (__u32 __user *)arg);
- if (retval == 0) {
- u32 save = spi->max_speed_hz;
-
- spi->max_speed_hz = tmp;
- retval = spi_setup(spi);
- if (retval >= 0)
- spidev->speed_hz = tmp;
- else
- dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
- spi->max_speed_hz = save;
- }
- break;
-
- default:
- /* segmented and/or full-duplex I/O request */
- /* Check message and copy into scratch area */
- ioc = spidev_get_ioc_message(cmd,
- (struct spi_ioc_transfer __user *)arg, &n_ioc);
- if (IS_ERR(ioc)) {
- retval = PTR_ERR(ioc);
- break;
- }
- if (!ioc)
- break; /* n_ioc is also 0 */
-
- /* translate to spi_message, execute */
- retval = spidev_message(spidev, ioc, n_ioc);
- kfree(ioc);
- break;
- }
-
- mutex_unlock(&spidev->buf_lock);
- spi_dev_put(spi);
- return retval;
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。