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函数
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
int spi_register_master(struct spi_master *master)
void spi_unregister_master(struct spi_master *master)
int spi_register_driver(struct spi_driver *sdrv)
void spi_unregister_driver(struct spi_driver *sdrv)
void spi_message_init(struct spi_message *m)
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
int spi_async(struct spi_device *spi, struct spi_message *message)
int spi_sync(struct spi_device *spi, struct spi_message *message)
int spi_write(struct spi_device *spi, const void *buf, size_t len)
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);
if (status < 0)
goto err1;
status = class_register(&spi_master_class);
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>;
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";
#address-cells = <1>;
#size-cells = <0>;
};
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;
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,
};
- 描述,与设备树相对应,会调用probe函数
- probe函数,匹配时调用
3.3.3 probe
static int spidev_probe(struct spi_device *spi)
{
struct spidev_data *spidev;
int status;
unsigned long minor;
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);
spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
if (!spidev)
return -ENOMEM;
spidev->spi = spi;
spin_lock_init(&spidev->spi_lock);
mutex_init(&spidev->buf_lock);
INIT_LIST_HEAD(&spidev->device_entry);
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'
#define SPI_IOC_RD_MODE _IOR(SPI_IOC_MAGIC, 1, __u8)
#define SPI_IOC_WR_MODE _IOW(SPI_IOC_MAGIC, 1, __u8)
#define SPI_IOC_RD_LSB_FIRST _IOR(SPI_IOC_MAGIC, 2, __u8)
#define SPI_IOC_WR_LSB_FIRST _IOW(SPI_IOC_MAGIC, 2, __u8)
#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)
#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)
#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;
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;
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;
}
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;
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;
}
|