SPI接口及驱动
Posted Arrow
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SPI接口及驱动相关的知识,希望对你有一定的参考价值。
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 ˉ \\barSS 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);
else
kfree(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)
else
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); (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;
else
status = 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接口及驱动的主要内容,如果未能解决你的问题,请参考以下文章