TinkerBoardR支持两路SPI,通过IO口引出。如下图所示,分别为SPI1和SPI5
源码采用的Linux内核版本为4.4,以下是Linux 4.4 spi驱动支持的一些特性:
标准的SPI有四个引脚,分别为:
SPI一共有四种工作模式,如下表,主机与从机需要工作在相同模式下才可以正常通信,实际上采用比较多的是模式0和模式3
SPI模式 | CPOL | CPHA | 空闲时SCK时钟 | 采样时刻 |
---|---|---|---|---|
0 | 0 | 0 | 低电平 | 奇数边沿 |
1 | 0 | 1 | 低电平 | 偶数边沿 |
2 | 1 | 0 | 高电平 | 奇数边沿 |
3 | 1 | 1 | 高电平 | 偶数边沿 |
下面我们来分析4种模式的时序图
CPOL=0:时钟线空闲时是低电平,第1个跳变沿是上升沿,第2个跳变沿是下降沿
CPHA=0:数据在第1个跳变沿(上升沿)采样
时序图如下:
CPOL=0:时钟线空闲时是低电平,第1个跳变沿是上升沿,第2个跳变沿是下降沿
CPHA=1:数据在第2个跳变沿(下降沿)采样
时序图如下:
CPOL=1:时钟线空闲时是高电平,第1个跳变沿是下降沿,第2个跳变沿是上升沿
CPHA=0:数据在第1个跳变沿(下降沿)采样
时序图如下:
CPOL=1:时钟线空闲时是高电平,第1个跳变沿是下降沿,第2个跳变沿是上升沿
CPHA=1:数据在第2个跳变沿(上升沿)采样
时序图如下:
对于SPI的驱动框架,大致可以分为三层
路径 | 描述 |
---|---|
kernel/drivers/spi/spi.c | spi驱动框架(核心层) |
kernel/drivers/spi/spi-rockchip.c | rk中spi各接口实现(soc中spi控制器驱动) |
kernel/drivers/spi/spidev.c | 创建spi设备节点,用户态使用 |
kernel/drivers/spi/spi-rockchip-test.c | spi测试驱动,需要自己手动添加到Makefile编译 |
Documentation/spi/spidev_test.c | 用户态spi测试工具 |
spi子系统主要有三个重要的数据结构,其中spi_master是控制器驱动,spi_device和spi_driver是从设备驱动
//kernel/drivers/spi/spi.c
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));
return 0;
err2:
bus_unregister(&spi_bus_type);
err1:
kfree(buf);
buf = NULL;
err0:
return status;
}
这里通过module_platform_driver(rockchip_spi_driver)来将spi控制器驱动注册到内核,后面会通过设备树的spi信息进行匹配,执行rockchip_spi_probe
//kernel/drivers/spi/spi-rockchip.c
static struct platform_driver rockchip_spi_driver = {
.driver = {
.name = DRIVER_NAME,
.pm = &rockchip_spi_pm,
.of_match_table = of_match_ptr(rockchip_spi_dt_match),
},
.probe = rockchip_spi_probe,
.remove = rockchip_spi_remove,
};
module_platform_driver(rockchip_spi_driver);
设备树信息会与下列信息进行匹配,匹配成功后进入rockchip_spi_probe
//kernel/drivers/spi/spi-rockchip.c
static const struct of_device_id rockchip_spi_dt_match[] = {
.........................
{ .compatible = "rockchip,rk3399-spi", },
{ },
};
MODULE_DEVICE_TABLE(of, rockchip_spi_dt_match);
设备树内容如下
//kernel/arch/arm64/boot/dts/rockchip/rk3399.dtsi
spi1: spi@ff1d0000 {
compatible = "rockchip,rk3399-spi", "rockchip,rk3066-spi";
reg = <0x0 0xff1d0000 0x0 0x1000>;
clocks = <&cru SCLK_SPI1>, <&cru PCLK_SPI1>;
clock-names = "spiclk", "apb_pclk";
interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH 0>;
dmas = <&dmac_peri 12>, <&dmac_peri 13>;
dma-names = "tx", "rx";
pinctrl-names = "default";
pinctrl-0 = <&spi1_clk &spi1_tx &spi1_rx &spi1_cs0>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
最后调用到rockchip_spi_probe
//kernel/drivers/spi/spi-rockchip.c
static int rockchip_spi_probe(struct platform_device *pdev)
{
..........
master = spi_alloc_master(&pdev->dev, sizeof(struct rockchip_spi)); //分配spi_master
..........
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);//从设备树中获取资源
rs->apb_pclk = devm_clk_get(&pdev->dev, "apb_pclk"); //名字刚好和dts对应上
..........
ret = devm_spi_register_master(&pdev->dev, master); //注册
}
接口层驱动主要在spidev.c中,先是一样的套路将驱动注册到内核
//kernel/drivers/spi/spidev.c
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, "spi", &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); //这里调用到spi核心层将spi驱动注册进去
if (status < 0) {
class_destroy(spidev_class);
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
}
return status;
}
module_init(spidev_init);
随后就是相关驱动操作结构体,与dts相匹配
//kernel/drivers/spi/spidev.c
static const struct of_device_id spidev_dt_ids[] = {
.......
{ .compatible = "rockchip,spidev" },
{},
};
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),
},
.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.
*/
};
SPI接口层设备树节点配置如下
//kernel/arch/arm64/boot/dts/rockchip/rk3399pro-tinker_edge_r.dtsi
&spi1 { //引用spi1控制器节点
status = "okay";
max-freq = <48000000>; /* spi internal clk, don't modify */
spi_dev@0 {
compatible = "rockchip,spidev"; //匹配驱动中对应的名字
reg = <0>; //片选0或者1
spi-max-frequency = <48000000>;
spi-lsb-first; //低位先行
};
};
匹配成功会进入probe
//kernel/drivers/spi/spidev.c
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));
}
/* 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);
...........
配置成功后,用户态在/dev目录下会有对应的spi节点可供操作。
首先,spi控制器驱动spi-rockchip.c通过从设备树中获取platform_device信息后,会加载相关的probe函数。随后在probe函数中分配了一个spi_master结构体代表了一个spi控制器或者总线,然后调用spi核心层的spi_register注册spi_master。接下来,在设备树中加入spi接口层相关的配置,随后注册spi_dev,然后与dts里面的信息进行匹配,执行spidev.c中的spidev_probe函数。
Kernel空间的SPI就是不提供给用户操作节点,只在内核模块间建立通信的一种方式。
使用make menuconfig命令修改我们的config文件
Device Drivers ‐‐‐>
[*] SPI support ‐‐‐>
<*> Rockchip SPI controller drive
&spi1 { //引用spi控制器节点
status = "okay";
max‐freq = <48000000>; //spi内部工作时钟
dma‐names = "tx","rx"; //使能DMA模式,一般通讯字节少于32字节的不建议用
spi_test@10 {
compatible ="rockchip,spi_test_bus1_cs0"; //与驱动对应的名字
reg = <0>; //片选0或者1
spi‐max‐frequency = <24000000>; //spi clk输出的时钟频率,不超过50M
spi‐cpha; //如果有配,cpha为1
spi‐cpol; //如果有配,cpol为1,clk脚保持高电平
pi‐cs‐high; //如果有配,每传完一个数据,cs都会被拉高,再拉低
status = "okay"; //使能设备节点
};
};
//简化版如下所示,一般配置这几个参数就能正常工作了
spi_test@11{
compatible ="rockchip,spi_test_bus1_cs1";
reg = <1>;
spi‐max‐frequency = <24000000>;
status = "okay";
};
static int spi_test_probe(struct spi_device *spi)
{
int ret;
int id = 0;
if(!spi)
return ‐ENOMEM;
spi‐>bits_per_word= 8;
ret= spi_setup(spi);
if(ret < 0) {
dev_err(&spi‐>dev,"ERR: fail to setup spi\n");
return‐1;
}
return ret;
}
static int spi_test_remove(struct spi_device *spi)
{
printk("%s\n",__func__);
return 0;
}
static const struct of_device_id spi_test_dt_match[]= {
{.compatible = "rockchip,spi_test_bus1_cs0", },
{.compatible = "rockchip,spi_test_bus1_cs1", },
{},
};
MODULE_DEVICE_TABLE(of,spi_test_dt_match);
static struct spi_driver spi_test_driver = {
.driver = {
.name = "spi_test",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(spi_test_dt_match),
},
.probe = spi_test_probe,
.remove = spi_test_remove,
};
static int __init spi_test_init(void)
{
int ret = 0;
ret = spi_register_driver(&spi_test_driver);
return ret;
}
device_initcall(spi_test_init);
static void __exit spi_test_exit(void)
{
return spi_unregister_driver(&spi_test_driver);
}
module_exit(spi_test_exit);
对SPI读写操作请参考include/linux/spi/spi.h,以下简单列出几个
static inline int spi_write(struct spi_device *spi,const void *buf, size_t len)
static inline int spi_read(struct spi_device *spi,void *buf, size_t len)
static inline int spi_write_and_read(structspi_device *spi, const void *tx_buf, void *rx_buf, size_t len)
首先需要手动添加kernel/drivers/spi/spi‐rockchip‐test.c的编译
//kernel/drivers/spi/Makefile
+obj-y += spi-rockchip-test.o
&spi0 {
status = "okay";
max-freq = <48000000>; //spi internal clk, don't modify
//dma-names = "tx", "rx"; //enable dma
pinctrl-names = "default"; //pinctrl according to you board
pinctrl-0 = <&spi0_clk &spi0_tx &spi0_rx &spi0_cs0 &spi0_cs1>;
spi_test@00 {
compatible = "rockchip,spi_test_bus0_cs0";
id = <0>; //这个属性spi-rockchip-test.c用来区分不同的spi从设备的
reg = <0>; //chip select 0:cs0 1:cs1
spi-max-frequency = <24000000>; //spi output clock
};
spi_test@01 {
compatible = "rockchip,spi_test_bus0_cs1";
id = <1>;
reg = <1>;
spi-max-frequency = <24000000>;
spi-slave-mode; 使能slave 模式, 只需改这里就行。
};
};
[0.530204]spi_test spi32766.0: fail to get poll_mode, default set 0
[0.530774]spi_test spi32766.0: fail to get type, default set 0
[0.531342]spi_test spi32766.0: fail to get enable_dma, default set 0
//以上这几个没配的话,不用管
[0.531929]rockchip_spi_test_probe:name=spi_test_bus1_cs0,bus_num=32766,cs=0,mode=0,speed=5000000
[0.532711]rockchip_spi_test_probe:poll_mode=0, type=0, enable_dma=0
//这是驱动注册成功的标志
echo write 0 10 255 > /dev/spi_misc_test
echo write 0 10 255 init.rc > /dev/spi_misc_test
echo read 0 10 255 > /dev/spi_misc_test
echo loop 0 10 255 > /dev/spi_misc_test
echo setspeed 0 1000000 > /dev/spi_misc_tes
//echo 类型 id 循环次数 传输长度 > /dev/spi_misc_test
//echo 类型 id 循环次数 传输长度 > /dev/spi_misc_test
//如有需要可以自行修改测试case
User mode SPI device 指的是用户空间直接操作SPI接口,这样方便众多的SPI外设驱动跑在用户空间,不需要改到内核,方便驱动移植开发。
使用make menuconfig命令修改我们的config文件
Device Drivers ‐‐‐>
[*] SPI support ‐‐‐>
[*] User mode SPI device driver support
&spi0 {
status = "okay";
max‐freq = <50000000>;
spi_test@00 {
compatible = "rockchip,spidev";
reg = <0>;
spi‐max‐frequency = <5000000>;
};
};
驱动设备加载注册成功后,会出现类似这个名字的设备:/dev/spidev1.1
请参照Documentation/spi/spidev_test.c