1. 简介
- SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构。支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSB first)。SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。
- SPI接口优点:
- 支持全双工操作
- 操作简单
- 数据传输速率较高(相对的)
- SPI接口缺点:
- 多个spi设备需要占用主机较多的管脚(每个从机都需要一根片选线)
- 只支持单个主机
- 没有指定的流控制,没有应答机制确认是否接收到数据
2. 接口
2.1 总线结构
2.2 硬件接口
- SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
- MOSI:主器件数据输出,从器件数据输入
- MISO:主器件数据输入,从器件数据输出
- SCLK: 时钟信号,由主器件产生
- S S ˉ \bar{SS} SSˉ: 从器件使能信号,由主器件控制
2.3 SPI工作模式
- SPI有四种工作模式,具体由CPOL(Clock Polarity 时钟极性),CPHA(Clock Phase时钟相位)决定
- 当CPOL为0时,空闲的时候SCLK电平是低电平
- 当CPOL为1时,空闲的时候SCLK电平是高电平
- 当CPHA为0时,采集数据发生在时钟周期的前边缘(第一个边缘,可能是上升缘也可能是下降缘,由CPOL决定),这同时意味着输出数据发生在后边缘
- 当CPHA为1时,采集数据发生在时钟周期的后边缘(第二个边缘,可能是上升缘也可能是下降缘,由CPOL决定),这同时意味着输出数据发生在前边缘
3. Linux驱动代码
-
Linux SPI驱动框架主要分为:
- 核心层
- 控制器驱动层
- 设备驱动层
-
最下层是硬件空间,SPI总线控制器,总线控制器负责硬件上的数据交互。内核空间中,需要有对应的控制器驱动,对硬件进行操作。
-
核心层的作用:向控制器驱动层提供注册SPI控制器驱动的接口,并提供一些需要控制器驱动实现的回调函数。核心层向上,对SPI设备驱动,提供标准的SPI收发API,以及设备注册函数。
-
当有SPI设备驱动发起一次传输时,设备驱动会调用SPI核心层的收发函数(spi_sync/spi_async),核心层的收发函数,会回调控制器驱动层实现的硬件相关的发送回调函数。从而实现SPI数据的收发。
-
我们编写SPI接口的设备驱动程序的时候,最需要关心的就是SPI控制器的部分和SPI设备采用的是那种模式,确定模式后,我们得将SPI控制器配置成一样的模式才能正常工作
-
SPI驱动代码:位于 kernel/drivers/spi目录下 (这个目录和一些层次比较明显的驱动目录布局不同,全放在这个文件夹下,因此还是只好通过看Kconfig 和 Makefile来找找思路)
-
相关数据结构:
- spi_master:SPI控制器
- spi_driver:SPI设备驱动
- spi_device:SPI设备
- spi_message:SPI传输数据结构体
- spi_transfer:该结构体是spi_message下的子单元
-
spi_driver和spi_device的关系
- spi_driver对应一套驱动方法,包含probe,remove等方法。spi_device对应真实的物理设备,每个spi设备都需要一个spi_device来描述。spi_driver与spi_device是一对多的关系,一个spi_driver上可以支持多个同类型的spi_device
-
spi_master和spi_device
- spi_master 与 spi_device 的关系和硬件上控制器与设备的关系一致,即spi_device依附于spi_master
-
spi_message和spi_transfer
- spi传输数据是以 spi_message 为单位的,我们需要传输的内容在 spi_transfer 中。spi_transfer是spi_message的子单元
- 将本次需要传输的 spi_transfer 以 spi_transfer->transfer_list 为链表项,连接成一个transfer_list链表,挂接在本次传输的spi_message spi_message->transfers链表下
- 将所有等待传输的 spi_message 以 spi_message->queue 为链表项,连接成个链表挂接在queue下
-
相关API函数
//分配一个spi_master
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)//注册和注销spi_master
int spi_register_master(struct spi_master *master)
void spi_unregister_master(struct spi_master *master)//注册和注销spi_driver
int spi_register_driver(struct spi_driver *sdrv)
void spi_unregister_driver(struct spi_driver *sdrv)//初始化spi_message
void spi_message_init(struct spi_message *m)
//向spi_message添加transfers
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
//异步发送spi_message
int spi_async(struct spi_device *spi, struct spi_message *message)
//同步发送spi_message
int spi_sync(struct spi_device *spi, struct spi_message *message)//spi同步写(封装了上面的函数)
int spi_write(struct spi_device *spi, const void *buf, size_t len)
//spi同步读(封装了上面的函数)
int spi_read(struct spi_device *spi, void *buf, size_t len)
//同步写并读取(封装了上面的函数)
int spi_write_then_read(struct spi_device *spi,const void *txbuf, unsigned n_tx,void *rxbuf, unsigned n_rx)
3.1 SPI核心层
- 实现代码:kernel/drivers/spi/spi.c (头文件位于: kernel/include/linux/spi/spi.h)
- 功能:(实现函数:spi_init)
- 对SPI子系统进行初始化工作
- 注册SPI总线
- 注册一个spi master(控制器)类
- 提供SPI设备驱动对SPI总线进行操作的API
struct bus_type spi_bus_type = {.name = "spi",.dev_groups = spi_dev_groups,.match = spi_match_device,.uevent = spi_uevent,
};static struct class spi_master_class = {.name = "spi_master",.owner = THIS_MODULE,.dev_release = spi_master_release,.dev_groups = spi_master_groups,
};
- spi_bus_type为spi总线类型,通过bus_register()函数将SPI 总线注册进总线,成功注册后,在/sys/bus 下即可找到spi 文件目录
- spi_master_class为spi控制器设备类,通过调用class_register()函数注册设备类,成功注册后,在/sys/class目录下即可找到spi_master文件目录
- spi子系统初始化函数spi_init,仅仅是注册了spi bus,以及spi_master class。
static int __init spi_init(void)
{int status;buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);if (!buf) {status = -ENOMEM;goto err0;}status = bus_register(&spi_bus_type); /*注册spi bus*/if (status < 0)goto err1;status = class_register(&spi_master_class); /* 注册spi_master类 */if (status < 0)goto err2;if (IS_ENABLED(CONFIG_OF_DYNAMIC))WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));if (IS_ENABLED(CONFIG_ACPI))WARN_ON(acpi_reconfig_notifier_register(&spi_acpi_notifier));return 0;
err2:bus_unregister(&spi_bus_type);
err1:kfree(buf);buf = NULL;
err0:return status;
}
3.2 SPI控制器驱动
- SPI控制器驱动:即SPI硬件控制器对应的驱动,核心部分需要实现硬件SPI数据收发功能。这样SPI设备驱动,才能通过SPI读写数据
- SPI是一种平台特定的资源,所以它是以platform device的方式注册进内核的,因此它的struct platform_device结构是已经静态定义好了的,现在只待它的struct platform_driver注册,然后和platform_device匹配。
- 描述SPI控制器的设备树节点:
spi0: spi@0xA5004000 {compatible = "my,my-spi"; /* 描述,和驱动匹配 */reg = <0 0xA5004000 0 0x1000>; /* 控制器IO地址 */clocks = <&spi0_mclk>; /* 控制器时钟 */clock-names = "spi_mclk"; /* 时钟名 */interrupt-parent = <&gic>;interrupts = <0 33 4>; /* 控制器中断 */resets = <&rst 0x50 4>; /* 复位寄存器 */reset-names = "spi0"; pinctrl-names = "default";pinctrl-0 = <&spi0_func>; /* 引脚复用功能配置 */status = "disabled"; /* 暂时disable */#address-cells = <1>;#size-cells = <0>;};
- 实现一个platform_driver
static struct platform_driver my_spi_driver = {.probe = my_spi_probe,.remove = my_spi_remove,.driver = {.name = MY_SPI_NAME,.of_match_table = my_spi_of_match,.pm = &my_spi_dev_pm_ops,},
};
3.2.1 probe函数功能
- 申请struct spi_master内存以及私有数据内存(struct my_spi)
- 将struct spi_master设置为platform_device的private_data
- 从设备树获取IO地址,对应设备树reg节点
- 将IO内存映射为虚拟地址
- 从设备树获取中断,对应设备树interrupts节点
- 申请中断,设置中断服务函数
- 设置核心层回调的片选使能函数,设置spi_master的transfer_one函数,如果控制器驱动不实现transfer和transfer_one_message,内核会自动填充默认的,最终控制器驱动只需要实现transfer_one。
- 根据时钟名,从设备树获取时钟
- 根据设备树的reset节点,操作寄存器spi控制器对应的BIT进行复位
- 调用devm_spi_register_master,把spi_master->device注册到设备模型中。核心层篇中以及详细介绍了devm_spi_register_master函数,
3.2.2 数据收发
- 数据收发部分主要有两个函数
- 1)控制收发的函数,即spi_master的spi_transfer_one函数
- 以看到全志这里代码的逻辑,一个是初始化完成量。然后进行一系列硬件操作后,睡眠等待完成信号,同时设置超时时间,超时了没有得到信号量,证明硬件出问题了,不应该一直等待,应该返回错误。
- 这个完成信号由中断来发送,硬件上会有发送完成中断。 - 2)配合发送的中断服务函数
- 读取中断状态寄存器,判断发送完成的BIT是否置位,如果置位,则表示硬件已经发送完数据了,那么发送完成信号量,给正在睡眠等待的spi_transfer_one函数。spi_transfer_one函数接收到信号量后被唤醒,知道硬件以及发送完数据了,返回0(表示发送成功)
3.3 SPI设备驱动
- 实现一个spi_driver, 并注册到SPI核心层
- 直接调用spi核心层提供的函数注册,也就是说它不需要关心是哪个控制器来实现最终的spi数据传输。从这里也可以看出核心层的作用,分隔了控制器和设备驱动的关联性,主要两边的驱动都容易实现
- 以内核中spidev设备驱动为例,对基于设备树的SPI设备驱动进行说明
- spidev驱动代码位于kernel/drivers/spi/spidev.c
- 设备树部分
dac0: dh2228@2 {compatible = "rohm,dh2228fv";reg = <2>;spi-max-frequency = <100000>;
};
- 设备树部分很简单,只是写了compatible描述,用于和驱动匹配。这里reg的意思是spi cs引脚序号。并不像其他平台设备一样是IO地址,或者像I2C一样是从机地址
3.3.1 spidev_init
static int __init spidev_init(void)
{int status;/* Claim our 256 reserved device numbers. Then register a class* that will key udev/mdev to add/remove /dev nodes. Last, register* the driver which manages those device numbers.*/BUILD_BUG_ON(N_SPI_MINORS > 256);status = register_chrdev(SPIDEV_MAJOR, "spidev_test", &spidev_fops);if (status < 0)return status;spidev_class = class_create(THIS_MODULE, "spidev");if (IS_ERR(spidev_class)) {unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);return PTR_ERR(spidev_class);}status = spi_register_driver(&spidev_spi_driver);if (status < 0) {class_destroy(spidev_class);unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);}return status;
}
module_init(spidev_init);static void __exit spidev_exit(void)
{spi_unregister_driver(&spidev_spi_driver);class_destroy(spidev_class);unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
}
module_exit(spidev_exit);
- 这里设置的spidev的file_operations(spidev_fops),设备操作函数后文详细介绍。然后创建了spidev的class,创建完成后在用户空间/sys/class/下可以看到spidev目录结构。然后调用spi_register_driver注册spi从设备。
3.3.2 spi_driver
static const struct of_device_id spidev_dt_ids[] = {{ .compatible = "rohm,dh2228fv" },{ .compatible = "lineartechnology,ltc2488" },{ .compatible = "ge,achc" },{ .compatible = "semtech,sx1301" },{},
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
static struct spi_driver spidev_spi_driver = {.driver = {.name = "spidev",.of_match_table = of_match_ptr(spidev_dt_ids),.acpi_match_table = ACPI_PTR(spidev_acpi_ids),},.probe = spidev_probe,.remove = spidev_remove,/* NOTE: suspend/resume methods are not necessary here.* We don't do anything except pass the requests to/from* the underlying controller. The refrigerator handles* most issues; the controller driver handles the rest.*/
};
- 描述,与设备树相对应,会调用probe函数
- probe函数,匹配时调用
3.3.3 probe
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.*/if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) {dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n");WARN_ON(spi->dev.of_node &&!of_match_device(spidev_dt_ids, &spi->dev));}spidev_probe_acpi(spi);/* Allocate driver data */spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);if (!spidev)return -ENOMEM;/* Initialize the driver data */spidev->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 = 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);}mutex_unlock(&device_list_lock);spidev->speed_hz = spi->max_speed_hz;if (status == 0)spi_set_drvdata(spi, spidev);elsekfree(spidev);return status;
}
- spidev的probe函数,申请了私有结构体spidev的内存空间,初始化了spidev的一些成员,自旋锁、互斥锁等。并将spidev指针设置为struct spi_device的私有数据(private_data)
- 调用device_create创建了/dev/下的spidev节点,如spi总线0上cs1设备,则设备名为/dev/spidev0.1,其他以此类推
- 在编写spi、i2c等驱动时,所做的操作也和spidev相差无几。初始化一些自己需要的资源等。但是一般情况下,这类涉及外设的驱动,会读取一下SPI、I2C从设备的ID寄存器等,来判断硬件是否就位,spi总线能否读写数据
3.3.4 spidev_fops
static const struct file_operations spidev_fops = {.owner = THIS_MODULE,.write = spidev_write,.read = spidev_read,.unlocked_ioctl = spidev_ioctl,.compat_ioctl = spidev_compat_ioctl,.open = spidev_open,.release = spidev_release,.llseek = no_llseek,
};
- open和release:判断是否申请txbuff和rxbuff内存,如果没申请就申请。并维护了一个user count,用户空间每打开一次就+1,关闭一次就-1。所有都关闭后release释放申请的内存。
3.3.4.1 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); (1)if (missing == 0)status = spidev_sync_write(spidev, count); (2)elsestatus = -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); (3)
}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;elsestatus = spi_sync(spi, message); (4)if (status == 0)status = message->actual_length;return status;
}
- copy_from_user:从用户空间拷贝要发送的数据到内核空间
- 调用spidev_sync_write函数发送数据,这个函数实现代码也在上面贴出来了
- spidev_sync_write代码实现,定义spi_transfer和spi_message,然后通过spidev_sync发送
- spidev_sync中仅仅是判断了spi_device是否为空,不为空则调用spi_sync函数将spi_message发送出去
- spidev_read函数与write函数如出一辙,不同的是write先从用户空间拷贝数据,然后调用发送函数发出去。struct spi_transfer填充的是tx_buff。而read则是先调用接受函数,struct spi_transfer填充的是rx_buff,然后将接收到的rx_buff,通过copy_to_user拷贝给用户空间。
3.3.4.2 ioctl
#define SPI_IOC_MAGIC 'k'/* Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) (limited to 8 bits) */
#define SPI_IOC_RD_MODE _IOR(SPI_IOC_MAGIC, 1, __u8)
#define SPI_IOC_WR_MODE _IOW(SPI_IOC_MAGIC, 1, __u8)/* Read / Write SPI bit justification */
#define SPI_IOC_RD_LSB_FIRST _IOR(SPI_IOC_MAGIC, 2, __u8)
#define SPI_IOC_WR_LSB_FIRST _IOW(SPI_IOC_MAGIC, 2, __u8)/* Read / Write SPI device word length (1..N) */
#define SPI_IOC_RD_BITS_PER_WORD _IOR(SPI_IOC_MAGIC, 3, __u8)
#define SPI_IOC_WR_BITS_PER_WORD _IOW(SPI_IOC_MAGIC, 3, __u8)/* Read / Write SPI device default max speed hz */
#define SPI_IOC_RD_MAX_SPEED_HZ _IOR(SPI_IOC_MAGIC, 4, __u32)
#define SPI_IOC_WR_MAX_SPEED_HZ _IOW(SPI_IOC_MAGIC, 4, __u32)/* Read / Write of the SPI mode field */
#define SPI_IOC_RD_MODE32 _IOR(SPI_IOC_MAGIC, 5, __u32)
#define SPI_IOC_WR_MODE32 _IOW(SPI_IOC_MAGIC, 5, __u32)
3.3.5 spidev总结
- spidev是内核中用来测试spi的驱动,不是专门针对某一SPI硬件设备做的驱动,只是简单的注册字符设备,用户空间通过read、write、ioctl,直接对spi进行操作,相当于是用户空间和spi设备的桥梁
- 用户空间操作时,打开设备节点,然后通过IOCTL设置模式、速度等参数,然后就可以调用read、write进行操作了
- 根据设备树获取节点信息
spi_register_master->of_register_spi_devices->of_register_spi_device->of_spi_parse_dt
static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,struct device_node *nc)
{u32 value;int rc;/* Mode (clock phase/polarity/etc.) */if (of_property_read_bool(nc, "spi-cpha"))spi->mode |= SPI_CPHA;if (of_property_read_bool(nc, "spi-cpol"))spi->mode |= SPI_CPOL;if (of_property_read_bool(nc, "spi-cs-high"))spi->mode |= SPI_CS_HIGH;if (of_property_read_bool(nc, "spi-3wire"))spi->mode |= SPI_3WIRE;if (of_property_read_bool(nc, "spi-lsb-first"))spi->mode |= SPI_LSB_FIRST;/* Device DUAL/QUAD mode */if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) {switch (value) {case 1:break;case 2:spi->mode |= SPI_TX_DUAL;break;case 4:spi->mode |= SPI_TX_QUAD;break;default:dev_warn(&ctlr->dev,"spi-tx-bus-width %d not supported\n",value);break;}}if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) {switch (value) {case 1:break;case 2:spi->mode |= SPI_RX_DUAL;break;case 4:spi->mode |= SPI_RX_QUAD;break;default:dev_warn(&ctlr->dev,"spi-rx-bus-width %d not supported\n",value);break;}}if (spi_controller_is_slave(ctlr)) {if (strcmp(nc->name, "slave")) {dev_err(&ctlr->dev, "%pOF is not called 'slave'\n",nc);return -EINVAL;}return 0;}/* Device address */rc = of_property_read_u32(nc, "reg", &value);if (rc) {dev_err(&ctlr->dev, "%pOF has no valid 'reg' property (%d)\n",nc, rc);return rc;}spi->chip_select = value;/* Device speed */rc = of_property_read_u32(nc, "spi-max-frequency", &value);if (rc) {dev_err(&ctlr->dev,"%pOF has no valid 'spi-max-frequency' property (%d)\n", nc, rc);return rc;}spi->max_speed_hz = value;return 0;
}