mirror of
https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git
synced 2025-12-01 12:31:01 +08:00
发布SPI: 21_编写SPI_Master驱动程序_新方法, 22_使用新方法编写的SPI_Master驱动程序上机实验
This commit is contained in:
79
IMX6ULL/doc_pic/11_SPI/21_编写SPI_Master驱动程序_新方法.md
Normal file
79
IMX6ULL/doc_pic/11_SPI/21_编写SPI_Master驱动程序_新方法.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# 编写SPI_Master驱动程序_新方法 #
|
||||
|
||||
本节源码:
|
||||

|
||||
|
||||
参考资料:
|
||||
|
||||
* 内核头文件:`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 总体框架
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 怎么编写SPI_Master驱动
|
||||
|
||||
#### 1.2.1 编写设备树
|
||||
|
||||
在设备树中,对于SPI Master,必须的属性如下:
|
||||
|
||||
* #address-cells:这个SPI Master下的SPI设备,需要多少个cell来表述它的片选引脚
|
||||
* #size-cells:必须设置为0
|
||||
* compatible:根据它找到SPI Master驱动
|
||||
|
||||
可选的属性如下:
|
||||
|
||||
* cs-gpios:SPI 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 数据传输流程
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 2.2 写代码
|
||||
|
||||
|
||||
|
||||
BIN
IMX6ULL/doc_pic/11_SPI/21_编写SPI_Master驱动程序_新方法.tif
Normal file
BIN
IMX6ULL/doc_pic/11_SPI/21_编写SPI_Master驱动程序_新方法.tif
Normal file
Binary file not shown.
206
IMX6ULL/doc_pic/11_SPI/22_使用新方法编写的SPI_Master驱动程序上机实验.md
Normal file
206
IMX6ULL/doc_pic/11_SPI/22_使用新方法编写的SPI_Master驱动程序上机实验.md
Normal file
@@ -0,0 +1,206 @@
|
||||
# 使用新方法编写的SPI_Master驱动程序上机实验 #
|
||||
|
||||
本节源码:
|
||||

|
||||
|
||||
参考资料:
|
||||
|
||||
* 内核头文件:`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`
|
||||
|
||||

|
||||
|
||||
* 更新设备树
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
|
||||
|
||||
BIN
IMX6ULL/doc_pic/11_SPI/22_使用新方法编写的SPI_Master驱动程序上机实验.tif
Normal file
BIN
IMX6ULL/doc_pic/11_SPI/22_使用新方法编写的SPI_Master驱动程序上机实验.tif
Normal file
Binary file not shown.
BIN
IMX6ULL/doc_pic/11_SPI/pic/88_spi_master_new_src.png
Normal file
BIN
IMX6ULL/doc_pic/11_SPI/pic/88_spi_master_new_src.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
IMX6ULL/doc_pic/11_SPI/pic/89_spi_master_new_src_ok.png
Normal file
BIN
IMX6ULL/doc_pic/11_SPI/pic/89_spi_master_new_src_ok.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
22
IMX6ULL/source/11_SPI/14_spi_master_new_ok/Makefile
Normal file
22
IMX6ULL/source/11_SPI/14_spi_master_new_ok/Makefile
Normal 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
|
||||
|
||||
|
||||
71
IMX6ULL/source/11_SPI/14_spi_master_new_ok/spi_test.c
Normal file
71
IMX6ULL/source/11_SPI/14_spi_master_new_ok/spi_test.c
Normal 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;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
};
|
||||
|
||||
139
IMX6ULL/source/11_SPI/14_spi_master_new_ok/virtual_spi_master.c
Normal file
139
IMX6ULL/source/11_SPI/14_spi_master_new_ok/virtual_spi_master.c
Normal 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");
|
||||
|
||||
@@ -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驱动程序上机实验
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
79
STM32MP157/doc_pic/11_SPI/21_编写SPI_Master驱动程序_新方法.md
Normal file
79
STM32MP157/doc_pic/11_SPI/21_编写SPI_Master驱动程序_新方法.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# 编写SPI_Master驱动程序_新方法 #
|
||||
|
||||
本节源码:
|
||||

|
||||
|
||||
参考资料:
|
||||
|
||||
* 内核头文件:`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 总体框架
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 怎么编写SPI_Master驱动
|
||||
|
||||
#### 1.2.1 编写设备树
|
||||
|
||||
在设备树中,对于SPI Master,必须的属性如下:
|
||||
|
||||
* #address-cells:这个SPI Master下的SPI设备,需要多少个cell来表述它的片选引脚
|
||||
* #size-cells:必须设置为0
|
||||
* compatible:根据它找到SPI Master驱动
|
||||
|
||||
可选的属性如下:
|
||||
|
||||
* cs-gpios:SPI 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 数据传输流程
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 2.2 写代码
|
||||
|
||||
|
||||
|
||||
BIN
STM32MP157/doc_pic/11_SPI/21_编写SPI_Master驱动程序_新方法.tif
Normal file
BIN
STM32MP157/doc_pic/11_SPI/21_编写SPI_Master驱动程序_新方法.tif
Normal file
Binary file not shown.
206
STM32MP157/doc_pic/11_SPI/22_使用新方法编写的SPI_Master驱动程序上机实验.md
Normal file
206
STM32MP157/doc_pic/11_SPI/22_使用新方法编写的SPI_Master驱动程序上机实验.md
Normal file
@@ -0,0 +1,206 @@
|
||||
# 使用新方法编写的SPI_Master驱动程序上机实验 #
|
||||
|
||||
本节源码:
|
||||

|
||||
|
||||
参考资料:
|
||||
|
||||
* 内核头文件:`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`
|
||||
|
||||

|
||||
|
||||
* 更新设备树
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
|
||||
|
||||
BIN
STM32MP157/doc_pic/11_SPI/22_使用新方法编写的SPI_Master驱动程序上机实验.tif
Normal file
BIN
STM32MP157/doc_pic/11_SPI/22_使用新方法编写的SPI_Master驱动程序上机实验.tif
Normal file
Binary file not shown.
BIN
STM32MP157/doc_pic/11_SPI/pic/88_spi_master_new_src.png
Normal file
BIN
STM32MP157/doc_pic/11_SPI/pic/88_spi_master_new_src.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
STM32MP157/doc_pic/11_SPI/pic/89_spi_master_new_src_ok.png
Normal file
BIN
STM32MP157/doc_pic/11_SPI/pic/89_spi_master_new_src_ok.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
22
STM32MP157/source/A7/11_SPI/14_spi_master_new_ok/Makefile
Normal file
22
STM32MP157/source/A7/11_SPI/14_spi_master_new_ok/Makefile
Normal 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
|
||||
|
||||
|
||||
71
STM32MP157/source/A7/11_SPI/14_spi_master_new_ok/spi_test.c
Normal file
71
STM32MP157/source/A7/11_SPI/14_spi_master_new_ok/spi_test.c
Normal 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;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user