发布SPI: 21_编写SPI_Master驱动程序_新方法, 22_使用新方法编写的SPI_Master驱动程序上机实验

This commit is contained in:
weidongshan
2022-06-15 15:51:44 +08:00
parent a04d7bfb13
commit 728e9185e6
22 changed files with 1124 additions and 1 deletions

View File

@@ -0,0 +1,79 @@
# 编写SPI_Master驱动程序_新方法 #
本节源码:
![image-20220614165137206](pic/88_spi_master_new_src.png)
参考资料:
* 内核头文件:`include\linux\spi\spi.h`
* 内核文档:`Documentation\devicetree\bindings\spi\spi-bus.txt`
* 内核源码:`drivers\spi\spi.c``drivers\spi\spi-sh.c`
## 1. SPI驱动框架
### 1.1 总体框架
![image-20220217163316229](pic/09_spi_drv_frame.png)
### 1.2 怎么编写SPI_Master驱动
#### 1.2.1 编写设备树
在设备树中对于SPI Master必须的属性如下
* #address-cells这个SPI Master下的SPI设备需要多少个cell来表述它的片选引脚
* #size-cells必须设置为0
* compatible根据它找到SPI Master驱动
可选的属性如下:
* cs-gpiosSPI Master可以使用多个GPIO当做片选可以在这个属性列出那些GPIO
* num-cs片选引脚总数
其他属性都是驱动程序相关的不同的SPI Master驱动程序要求的属性可能不一样。
在SPI Master对应的设备树节点下每一个子节点都对应一个SPI设备这个SPI设备连接在该SPI Master下面。
这些子节点中,必选的属性如下:
* compatible根据它找到SPI Device驱动
* reg用来表示它使用哪个片选引脚
* spi-max-frequency必选该SPI设备支持的最大SPI时钟
可选的属性如下:
* spi-cpol这是一个空属性(没有值)表示CPOL为1即平时SPI时钟为低电平
* spi-cpha这是一个空属性(没有值)表示CPHA为1)即在时钟的第2个边沿采样数据
* spi-cs-high这是一个空属性(没有值),表示片选引脚高电平有效
* spi-3wire这是一个空属性(没有值)表示使用SPI 三线模式
* spi-lsb-first这是一个空属性(没有值)表示使用SPI传输数据时先传输最低位(LSB)
* spi-tx-bus-width表示有几条MOSI引脚没有这个属性时默认只有1条MOSI引脚
* spi-rx-bus-width表示有几条MISO引脚没有这个属性时默认只有1条MISO引脚
* spi-rx-delay-us单位是毫秒表示每次读传输后要延时多久
* spi-tx-delay-us单位是毫秒表示每次写传输后要延时多久
#### 1.2.2 编写驱动程序
* 核心为:分配/设置/注册spi_master结构体
* 对于老方法spi_master结构体的核心是transfer函数
## 2. 编写程序
### 2.1 数据传输流程
![](pic/85_spi_transfer_new.png)
### 2.2 写代码

View File

@@ -0,0 +1,206 @@
# 使用新方法编写的SPI_Master驱动程序上机实验 #
本节源码:
![image-20220614170117164](pic/89_spi_master_new_src_ok.png)
参考资料:
* 内核头文件:`include\linux\spi\spi.h`
* 内核文档:`Documentation\devicetree\bindings\spi\spi-bus.txt`
* 内核源码:`drivers\spi\spi.c``drivers\spi\spi-sh.c`
## 1. 修改设备树
### 1.1 IMX6ULL
修改`arch/arm/boot/dts/100ask_imx6ull-14x14.dts`中,如下:
```shell
virtual_spi_master {
compatible = "100ask,virtual_spi_master";
status = "okay";
cs-gpios = <&gpio4 27 GPIO_ACTIVE_LOW>;
num-chipselects = <1>;
#address-cells = <1>;
#size-cells = <0>;
virtual_spi_dev: virtual_spi_dev@0 {
compatible = "spidev";
reg = <0>;
spi-max-frequency = <100000>;
};
};
```
### 2.2 STM32MP157
修改`arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dts`中,如下:
```shell
virtual_spi_master {
compatible = "100ask,virtual_spi_master";
status = "okay";
cs-gpios = <&gpioh 6 GPIO_ACTIVE_LOW>;
num-chipselects = <1>;
#address-cells = <1>;
#size-cells = <0>;
virtual_spi_dev: virtual_spi_dev@0 {
compatible = "spidev";
reg = <0>;
spi-max-frequency = <100000>;
};
};
```
## 2. 编译替换设备树
### 2.1 IMX6ULL
#### 2.1.1 设置工具链
```shell
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
```
#### 2.1.2 编译、替换设备树
* 编译设备树:
在Ubuntu的IMX6ULL内核目录下执行如下命令,
得到设备树文件:`arch/arm/boot/dts/100ask_imx6ull-14x14.dtb`
```shell
make dtbs
```
* 复制到NFS目录
```shell
$ cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/
```
* 开发板上挂载NFS文件系统
```shell
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
```
* 更新设备树
```shell
[root@100ask:~]# cp /mnt/100ask_imx6ull-14x14.dtb /boot
[root@100ask:~]# sync
```
* 重启开发板
### 2.2 STM32MP157
#### 2.2.1 设置工具链
```shell
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
```
#### 2.2.2 编译、替换设备树
* 编译设备树:
在Ubuntu的STM32MP157内核目录下执行如下命令,
得到设备树文件:`arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb`
```shell
make dtbs
```
* 复制到NFS目录
```shell
$ cp arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb ~/nfs_rootfs/
```
* 开发板上挂载NFS文件系统
```shell
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
```
* 确定设备树分区挂载在哪里
由于版本变化STM32MP157单板上烧录的系统可能有细微差别。
在开发板上执行`cat /proc/mounts`后,可以得到两种结果(见下图)
* mmcblk2p2分区挂载在/boot目录下(下图左边):无需特殊操作,下面把文件复制到/boot目录即可
* mmcblk2p2挂载在/mnt目录下(下图右边)
* 在视频里、后面文档里,都是更新/boot目录下的文件所以要先执行以下命令重新挂载
* `mount /dev/mmcblk2p2 /boot`
![](pic/46_boot_mount.png)
* 更新设备树
```shell
[root@100ask:~]# cp /mnt/stm32mp157c-100ask-512d-lcd-v1.dtb /boot/
[root@100ask:~]# sync
```
* 重启开发板
## 3. 编译驱动和APP
## 4. 上机实验
先执行:
```shell
insmod virtual_spi_master.ko
```
对于IMX6ULL因为spidev没有被编译进内核那么再执行(对于STM32MP157无需执行这个命令)
```shell
insmod spidev.ko
```
确定设备节点:
```shell
ls /dev/spidev*
```
假设设备节点为`/dev/spidev32765.0`,执行测试程序(它并不会真正地读写数据)
```shell
./spi_test /dev/spidev32765.0 500
./spi_test /dev/spidev32765.0 600
./spi_test /dev/spidev32765.0 1000
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -12,30 +12,78 @@
#include <linux/spi/spi.h>
static struct spi_master *g_virtual_master;
static struct spi_bitbang *g_virtual_bitbang;
static struct completion g_xfer_done;
static const struct of_device_id spi_virtual_dt_ids[] = {
{ .compatible = "100ask,virtual_spi_master", },
{ /* sentinel */ }
};
/* xxx_isr() { complete(&g_xfer_done) } */
static int spi_virtual_transfer(struct spi_device *spi,
struct spi_transfer *transfer)
{
int timeout;
#if 1
/* 1. init complete */
reinit_completion(&g_xfer_done);
/* 2. 启动硬件传输 */
complete(&g_xfer_done);
/* 3. wait for complete */
timeout = wait_for_completion_timeout(&g_xfer_done,
100);
if (!timeout) {
dev_err(&spi->dev, "I/O Error in PIO\n");
return -ETIMEDOUT;
}
#endif
return transfer->len;
}
static int spi_virtual_probe(struct platform_device *pdev)
{
struct spi_master *master;
int ret;
/* 分配/设置/注册spi_master */
g_virtual_master = master = spi_alloc_master(&pdev->dev, 0);
g_virtual_master = master = spi_alloc_master(&pdev->dev, sizeof(struct spi_bitbang));
if (master == NULL) {
dev_err(&pdev->dev, "spi_alloc_master error.\n");
return -ENOMEM;
}
g_virtual_bitbang = spi_master_get_devdata(master);
/* 怎么设置spi_master?
* 1. spi_master使用默认的函数
* 2. 分配/设置 spi_bitbang结构体: 主要是实现里面的txrx_bufs函数
* 3. spi_master要能找到spi_bitbang
*/
g_virtual_bitbang->master = master;
g_virtual_bitbang->txrx_bufs = spi_virtual_transfer;
#if 0
ret = spi_register_master(master);
if (ret < 0) {
printk(KERN_ERR "spi_register_master error.\n");
spi_master_put(master);
return ret;
}
#else
ret = spi_bitbang_start(g_virtual_bitbang);
if (ret) {
printk("bitbang start failed with %d\n", ret);
return ret;
}
#endif
return 0;
@@ -44,8 +92,12 @@ static int spi_virtual_probe(struct platform_device *pdev)
static int spi_virtual_remove(struct platform_device *pdev)
{
#if 0
/* 反注册spi_master */
spi_unregister_master(g_virtual_master);
#endif
spi_bitbang_stop(g_virtual_bitbang);
spi_master_put(g_virtual_master);
return 0;
}

View File

@@ -0,0 +1,22 @@
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o spi_test spi_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order spi_test
obj-m += virtual_spi_master.o

View File

@@ -0,0 +1,71 @@
/* 参考: tools\spi\spidev_fdx.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <errno.h>
/* dac_test /dev/spidevB.D <val> */
int main(int argc, char **argv)
{
int fd;
unsigned int val;
struct spi_ioc_transfer xfer[1];
int status;
unsigned char tx_buf[2];
unsigned char rx_buf[2];
if (argc != 3)
{
printf("Usage: %s /dev/spidevB.D <val>\n", argv[0]);
return 0;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("can not open %s\n", argv[1]);
return 1;
}
val = strtoul(argv[2], NULL, 0);
val <<= 2; /* bit0,bit1 = 0b00 */
val &= 0xFFC; /* 只保留10bit */
tx_buf[1] = val & 0xff;
tx_buf[0] = (val>>8) & 0xff;
memset(xfer, 0, sizeof xfer);
xfer[0].tx_buf = tx_buf;
xfer[0].rx_buf = rx_buf;
xfer[0].len = 2;
status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
if (status < 0) {
printf("SPI_IOC_MESSAGE %d\n", errno);
return -1;
}
/* 打印 */
val = (rx_buf[0] << 8) | (rx_buf[1]);
val >>= 2;
printf("Pre val = %d\n", val);
return 0;
}

View File

@@ -0,0 +1,15 @@
vitural_spi_master {
compatible = "100ask,virtual_spi_master";
status = "okay";
cs-gpios = <&gpio4 27 GPIO_ACTIVE_LOW>;
num-chipselects = <1>;
#address-cells = <1>;
#size-cells = <0>;
virtual_spi_dev: virtual_spi_dev@0 {
compatible = "spidev";
reg = <0>;
spi-max-frequency = <100000>;
};
};

View File

@@ -0,0 +1,139 @@
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_bitbang.h>
static struct spi_master *g_virtual_master;
static struct spi_bitbang *g_virtual_bitbang;
static struct completion g_xfer_done;
static const struct of_device_id spi_virtual_dt_ids[] = {
{ .compatible = "100ask,virtual_spi_master", },
{ /* sentinel */ }
};
/* xxx_isr() { complete(&g_xfer_done) } */
static int spi_virtual_transfer(struct spi_device *spi,
struct spi_transfer *transfer)
{
int timeout;
#if 1
/* 1. init complete */
reinit_completion(&g_xfer_done);
/* 2. 启动硬件传输 */
complete(&g_xfer_done);
/* 3. wait for complete */
timeout = wait_for_completion_timeout(&g_xfer_done,
100);
if (!timeout) {
dev_err(&spi->dev, "I/O Error in PIO\n");
return -ETIMEDOUT;
}
#endif
return transfer->len;
}
static void spi_virtual_chipselect(struct spi_device *spi, int is_on)
{
}
static int spi_virtual_probe(struct platform_device *pdev)
{
struct spi_master *master;
int ret;
/* 分配/设置/注册spi_master */
g_virtual_master = master = spi_alloc_master(&pdev->dev, sizeof(struct spi_bitbang));
if (master == NULL) {
dev_err(&pdev->dev, "spi_alloc_master error.\n");
return -ENOMEM;
}
g_virtual_bitbang = spi_master_get_devdata(master);
init_completion(&g_xfer_done);
/* 怎么设置spi_master?
* 1. spi_master使用默认的函数
* 2. 分配/设置 spi_bitbang结构体: 主要是实现里面的txrx_bufs函数
* 3. spi_master要能找到spi_bitbang
*/
g_virtual_bitbang->master = master;
g_virtual_bitbang->txrx_bufs = spi_virtual_transfer;
g_virtual_bitbang->chipselect = spi_virtual_chipselect;
master->dev.of_node = pdev->dev.of_node;
#if 0
ret = spi_register_master(master);
if (ret < 0) {
printk(KERN_ERR "spi_register_master error.\n");
spi_master_put(master);
return ret;
}
#else
ret = spi_bitbang_start(g_virtual_bitbang);
if (ret) {
printk("bitbang start failed with %d\n", ret);
return ret;
}
#endif
return 0;
}
static int spi_virtual_remove(struct platform_device *pdev)
{
#if 0
/* 反注册spi_master */
spi_unregister_master(g_virtual_master);
#endif
spi_bitbang_stop(g_virtual_bitbang);
spi_master_put(g_virtual_master);
return 0;
}
static struct platform_driver spi_virtual_driver = {
.probe = spi_virtual_probe,
.remove = spi_virtual_remove,
.driver = {
.name = "virtual_spi",
.of_match_table = spi_virtual_dt_ids,
},
};
static int virtual_master_init(void)
{
return platform_driver_register(&spi_virtual_driver);
}
static void virtual_master_exit(void)
{
platform_driver_unregister(&spi_virtual_driver);
}
module_init(virtual_master_init);
module_exit(virtual_master_exit);
MODULE_DESCRIPTION("Virtual SPI bus driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.100ask.net");

View File

@@ -596,6 +596,13 @@ git clone https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git
20_使用老方法编写的SPI_Master驱动程序上机实验_STM32MP157
```
* 2021.06.15 发布"SPI子系统"
```SHELL
21_编写SPI_Master驱动程序_新方法
22_使用新方法编写的SPI_Master驱动程序上机实验
```

View File

@@ -0,0 +1,79 @@
# 编写SPI_Master驱动程序_新方法 #
本节源码:
![image-20220614165137206](pic/88_spi_master_new_src.png)
参考资料:
* 内核头文件:`include\linux\spi\spi.h`
* 内核文档:`Documentation\devicetree\bindings\spi\spi-bus.txt`
* 内核源码:`drivers\spi\spi.c``drivers\spi\spi-sh.c`
## 1. SPI驱动框架
### 1.1 总体框架
![image-20220217163316229](pic/09_spi_drv_frame.png)
### 1.2 怎么编写SPI_Master驱动
#### 1.2.1 编写设备树
在设备树中对于SPI Master必须的属性如下
* #address-cells这个SPI Master下的SPI设备需要多少个cell来表述它的片选引脚
* #size-cells必须设置为0
* compatible根据它找到SPI Master驱动
可选的属性如下:
* cs-gpiosSPI Master可以使用多个GPIO当做片选可以在这个属性列出那些GPIO
* num-cs片选引脚总数
其他属性都是驱动程序相关的不同的SPI Master驱动程序要求的属性可能不一样。
在SPI Master对应的设备树节点下每一个子节点都对应一个SPI设备这个SPI设备连接在该SPI Master下面。
这些子节点中,必选的属性如下:
* compatible根据它找到SPI Device驱动
* reg用来表示它使用哪个片选引脚
* spi-max-frequency必选该SPI设备支持的最大SPI时钟
可选的属性如下:
* spi-cpol这是一个空属性(没有值)表示CPOL为1即平时SPI时钟为低电平
* spi-cpha这是一个空属性(没有值)表示CPHA为1)即在时钟的第2个边沿采样数据
* spi-cs-high这是一个空属性(没有值),表示片选引脚高电平有效
* spi-3wire这是一个空属性(没有值)表示使用SPI 三线模式
* spi-lsb-first这是一个空属性(没有值)表示使用SPI传输数据时先传输最低位(LSB)
* spi-tx-bus-width表示有几条MOSI引脚没有这个属性时默认只有1条MOSI引脚
* spi-rx-bus-width表示有几条MISO引脚没有这个属性时默认只有1条MISO引脚
* spi-rx-delay-us单位是毫秒表示每次读传输后要延时多久
* spi-tx-delay-us单位是毫秒表示每次写传输后要延时多久
#### 1.2.2 编写驱动程序
* 核心为:分配/设置/注册spi_master结构体
* 对于老方法spi_master结构体的核心是transfer函数
## 2. 编写程序
### 2.1 数据传输流程
![](pic/85_spi_transfer_new.png)
### 2.2 写代码

View File

@@ -0,0 +1,206 @@
# 使用新方法编写的SPI_Master驱动程序上机实验 #
本节源码:
![image-20220614170117164](pic/89_spi_master_new_src_ok.png)
参考资料:
* 内核头文件:`include\linux\spi\spi.h`
* 内核文档:`Documentation\devicetree\bindings\spi\spi-bus.txt`
* 内核源码:`drivers\spi\spi.c``drivers\spi\spi-sh.c`
## 1. 修改设备树
### 1.1 IMX6ULL
修改`arch/arm/boot/dts/100ask_imx6ull-14x14.dts`中,如下:
```shell
virtual_spi_master {
compatible = "100ask,virtual_spi_master";
status = "okay";
cs-gpios = <&gpio4 27 GPIO_ACTIVE_LOW>;
num-chipselects = <1>;
#address-cells = <1>;
#size-cells = <0>;
virtual_spi_dev: virtual_spi_dev@0 {
compatible = "spidev";
reg = <0>;
spi-max-frequency = <100000>;
};
};
```
### 2.2 STM32MP157
修改`arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dts`中,如下:
```shell
virtual_spi_master {
compatible = "100ask,virtual_spi_master";
status = "okay";
cs-gpios = <&gpioh 6 GPIO_ACTIVE_LOW>;
num-chipselects = <1>;
#address-cells = <1>;
#size-cells = <0>;
virtual_spi_dev: virtual_spi_dev@0 {
compatible = "spidev";
reg = <0>;
spi-max-frequency = <100000>;
};
};
```
## 2. 编译替换设备树
### 2.1 IMX6ULL
#### 2.1.1 设置工具链
```shell
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
```
#### 2.1.2 编译、替换设备树
* 编译设备树:
在Ubuntu的IMX6ULL内核目录下执行如下命令,
得到设备树文件:`arch/arm/boot/dts/100ask_imx6ull-14x14.dtb`
```shell
make dtbs
```
* 复制到NFS目录
```shell
$ cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/
```
* 开发板上挂载NFS文件系统
```shell
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
```
* 更新设备树
```shell
[root@100ask:~]# cp /mnt/100ask_imx6ull-14x14.dtb /boot
[root@100ask:~]# sync
```
* 重启开发板
### 2.2 STM32MP157
#### 2.2.1 设置工具链
```shell
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
```
#### 2.2.2 编译、替换设备树
* 编译设备树:
在Ubuntu的STM32MP157内核目录下执行如下命令,
得到设备树文件:`arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb`
```shell
make dtbs
```
* 复制到NFS目录
```shell
$ cp arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb ~/nfs_rootfs/
```
* 开发板上挂载NFS文件系统
```shell
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
```
* 确定设备树分区挂载在哪里
由于版本变化STM32MP157单板上烧录的系统可能有细微差别。
在开发板上执行`cat /proc/mounts`后,可以得到两种结果(见下图)
* mmcblk2p2分区挂载在/boot目录下(下图左边):无需特殊操作,下面把文件复制到/boot目录即可
* mmcblk2p2挂载在/mnt目录下(下图右边)
* 在视频里、后面文档里,都是更新/boot目录下的文件所以要先执行以下命令重新挂载
* `mount /dev/mmcblk2p2 /boot`
![](pic/46_boot_mount.png)
* 更新设备树
```shell
[root@100ask:~]# cp /mnt/stm32mp157c-100ask-512d-lcd-v1.dtb /boot/
[root@100ask:~]# sync
```
* 重启开发板
## 3. 编译驱动和APP
## 4. 上机实验
先执行:
```shell
insmod virtual_spi_master.ko
```
对于IMX6ULL因为spidev没有被编译进内核那么再执行(对于STM32MP157无需执行这个命令)
```shell
insmod spidev.ko
```
确定设备节点:
```shell
ls /dev/spidev*
```
假设设备节点为`/dev/spidev32765.0`,执行测试程序(它并不会真正地读写数据)
```shell
./spi_test /dev/spidev32765.0 500
./spi_test /dev/spidev32765.0 600
./spi_test /dev/spidev32765.0 1000
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,22 @@
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o spi_test spi_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order spi_test
obj-m += virtual_spi_master.o

View File

@@ -0,0 +1,71 @@
/* 参考: tools\spi\spidev_fdx.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <errno.h>
/* dac_test /dev/spidevB.D <val> */
int main(int argc, char **argv)
{
int fd;
unsigned int val;
struct spi_ioc_transfer xfer[1];
int status;
unsigned char tx_buf[2];
unsigned char rx_buf[2];
if (argc != 3)
{
printf("Usage: %s /dev/spidevB.D <val>\n", argv[0]);
return 0;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("can not open %s\n", argv[1]);
return 1;
}
val = strtoul(argv[2], NULL, 0);
val <<= 2; /* bit0,bit1 = 0b00 */
val &= 0xFFC; /* 只保留10bit */
tx_buf[1] = val & 0xff;
tx_buf[0] = (val>>8) & 0xff;
memset(xfer, 0, sizeof xfer);
xfer[0].tx_buf = tx_buf;
xfer[0].rx_buf = rx_buf;
xfer[0].len = 2;
status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
if (status < 0) {
printf("SPI_IOC_MESSAGE %d\n", errno);
return -1;
}
/* 打印 */
val = (rx_buf[0] << 8) | (rx_buf[1]);
val >>= 2;
printf("Pre val = %d\n", val);
return 0;
}

View File

@@ -0,0 +1,15 @@
virtual_spi_master {
compatible = "100ask,virtual_spi_master";
status = "okay";
cs-gpios = <&gpioh 6 GPIO_ACTIVE_LOW>;
num-chipselects = <1>;
#address-cells = <1>;
#size-cells = <0>;
virtual_spi_dev: virtual_spi_dev@0 {
compatible = "spidev";
reg = <0>;
spi-max-frequency = <100000>;
};
};

View File

@@ -0,0 +1,139 @@
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_bitbang.h>
static struct spi_master *g_virtual_master;
static struct spi_bitbang *g_virtual_bitbang;
static struct completion g_xfer_done;
static const struct of_device_id spi_virtual_dt_ids[] = {
{ .compatible = "100ask,virtual_spi_master", },
{ /* sentinel */ }
};
/* xxx_isr() { complete(&g_xfer_done) } */
static int spi_virtual_transfer(struct spi_device *spi,
struct spi_transfer *transfer)
{
int timeout;
#if 1
/* 1. init complete */
reinit_completion(&g_xfer_done);
/* 2. 启动硬件传输 */
complete(&g_xfer_done);
/* 3. wait for complete */
timeout = wait_for_completion_timeout(&g_xfer_done,
100);
if (!timeout) {
dev_err(&spi->dev, "I/O Error in PIO\n");
return -ETIMEDOUT;
}
#endif
return transfer->len;
}
static void spi_virtual_chipselect(struct spi_device *spi, int is_on)
{
}
static int spi_virtual_probe(struct platform_device *pdev)
{
struct spi_master *master;
int ret;
/* 分配/设置/注册spi_master */
g_virtual_master = master = spi_alloc_master(&pdev->dev, sizeof(struct spi_bitbang));
if (master == NULL) {
dev_err(&pdev->dev, "spi_alloc_master error.\n");
return -ENOMEM;
}
g_virtual_bitbang = spi_master_get_devdata(master);
init_completion(&g_xfer_done);
/* 怎么设置spi_master?
* 1. spi_master使用默认的函数
* 2. 分配/设置 spi_bitbang结构体: 主要是实现里面的txrx_bufs函数
* 3. spi_master要能找到spi_bitbang
*/
g_virtual_bitbang->master = master;
g_virtual_bitbang->txrx_bufs = spi_virtual_transfer;
g_virtual_bitbang->chipselect = spi_virtual_chipselect;
master->dev.of_node = pdev->dev.of_node;
#if 0
ret = spi_register_master(master);
if (ret < 0) {
printk(KERN_ERR "spi_register_master error.\n");
spi_master_put(master);
return ret;
}
#else
ret = spi_bitbang_start(g_virtual_bitbang);
if (ret) {
printk("bitbang start failed with %d\n", ret);
return ret;
}
#endif
return 0;
}
static int spi_virtual_remove(struct platform_device *pdev)
{
#if 0
/* 反注册spi_master */
spi_unregister_master(g_virtual_master);
#endif
spi_bitbang_stop(g_virtual_bitbang);
spi_master_put(g_virtual_master);
return 0;
}
static struct platform_driver spi_virtual_driver = {
.probe = spi_virtual_probe,
.remove = spi_virtual_remove,
.driver = {
.name = "virtual_spi",
.of_match_table = spi_virtual_dt_ids,
},
};
static int virtual_master_init(void)
{
return platform_driver_register(&spi_virtual_driver);
}
static void virtual_master_exit(void)
{
platform_driver_unregister(&spi_virtual_driver);
}
module_init(virtual_master_init);
module_exit(virtual_master_exit);
MODULE_DESCRIPTION("Virtual SPI bus driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.100ask.net");