diff --git a/IMX6ULL/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.md b/IMX6ULL/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.md index 6e47855..4bc299f 100644 --- a/IMX6ULL/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.md +++ b/IMX6ULL/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.md @@ -4,12 +4,25 @@ * DAC芯片手册:`TLC5615.pdf` - -## 1. 硬件 -### 1.1 原理图 +## 1. 要做什么事情 + +![](pic/09_spi_drv_frame.png) + + + +* 查看原理图,编写设备树 +* 编写驱动程序,注册一个spidrv +* 编写测试程序 + + + + +## 2. 硬件 + +### 2.1 原理图 IMX6ULL: @@ -27,9 +40,9 @@ STM32MP157: -### 1.2 连接 +### 2.2 连接 -#### 1.2.1 IMX6ULL +#### 2.2.1 IMX6ULL DAC模块接到IMX6ULL扩展板的SPI_A插座上: @@ -37,11 +50,11 @@ DAC模块接到IMX6ULL扩展板的SPI_A插座上: -#### 1.2.2 STM32MP157 +#### 2.2.2 STM32MP157 -## 2. 编写设备树 +## 3. 编写设备树 确认SPI时钟最大频率: @@ -66,7 +79,7 @@ F = 20000000 = 20MHz -### 2.1 IMX6ULL +### 3.1 IMX6ULL ![image-20220311101017666](pic/44_imx6ull_pro_extend_spi_a.png) @@ -95,7 +108,7 @@ DAC模块接在这个插座上,那么要在设备树里spi1的节点下创建 -### 2.2 STM32MP157 +### 3.2 STM32MP157 ![image-20220311101127305](pic/45_stm32mp157_pro_extend_spi_a.png) @@ -129,7 +142,7 @@ DAC模块接在这个插座上,那么要在设备树里spi5的节点下创建 -## 3. 编写驱动程序 +## 4. 编写驱动程序 以前我们基于spidev编写过DAC的应用程序,可以参考它: @@ -139,5 +152,5 @@ DAC模块接在这个插座上,那么要在设备树里spi5的节点下创建 -## 4. 编写测试程序 +## 5. 编写测试程序 diff --git a/IMX6ULL/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.tif b/IMX6ULL/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.tif index d28a9c2..56000f6 100644 Binary files a/IMX6ULL/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.tif and b/IMX6ULL/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.tif differ diff --git a/IMX6ULL/doc_pic/11_SPI/13_编写DAC驱动_上机实验.md b/IMX6ULL/doc_pic/11_SPI/13_编写DAC驱动_上机实验.md new file mode 100644 index 0000000..10c1161 --- /dev/null +++ b/IMX6ULL/doc_pic/11_SPI/13_编写DAC驱动_上机实验.md @@ -0,0 +1,286 @@ +# 编写DAC驱动_上机实验 # + +* 源码 + +![image-20220426095853618](pic/73_src_dac_drv.png) + + + +## 1. 硬件 + +### 1.1 原理图 + +IMX6ULL: + +![image-20220309150927785](pic/33_imx6ull_dac.png) + +STM32MP157: + +![image-20220309151025637](pic/34_stm32mp157_dac.png) + + + +原理图: + +![image-20220309151636533](pic/35_dac_sch.png) + + + +### 1.2 连接 + +#### 1.2.1 IMX6ULL + +DAC模块接到IMX6ULL扩展板的SPI_A插座上: + +![image-20220309164031109](pic/40_dac_on_imx6ull.png) + + + +#### 1.2.2 STM32MP157 + +DAC模块接到STM32MP157扩展板的SPI_A插座上: + +![image-20220311100618477](pic/43_dac_on_stm32mp157.png) + + + + + +## 2. 编写设备树 + +确认SPI时钟最大频率: + +![image-20220309163435541](pic/39_dac_time_param.png) + +```shell +T = 25 + 25 = 50ns +F = 20000000 = 20MHz +``` + + + +设备树如下: + +```shell + dac: dac { + compatible = "100ask,dac"; + reg = <0>; + spi-max-frequency = <20000000>; + }; +``` + + + +### 2.1 IMX6ULL + +![image-20220311101017666](pic/44_imx6ull_pro_extend_spi_a.png) + +DAC模块接在这个插座上,那么要在设备树里spi1的节点下创建子节点。 + +代码在`arch/arm/boot/dts/100ask_imx6ull-14x14.dts`中,如下: + +```shell +&ecspi1 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_ecspi1>; + + fsl,spi-num-chipselects = <2>; + cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>; + status = "okay"; + + dac: dac { + compatible = "100ask,dac"; + reg = <0>; + spi-max-frequency = <20000000>; + }; +}; +``` + + + + + +### 2.2 STM32MP157 + +![image-20220311101127305](pic/45_stm32mp157_pro_extend_spi_a.png) + +DAC模块接在这个插座上,那么要在设备树里spi5的节点下创建子节点。 + +代码在`arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dts`中,如下: + +```shell +&spi5 { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&spi5_pins_a>; + pinctrl-1 = <&spi5_sleep_pins_a>; + status = "okay"; + cs-gpios = <&gpioh 5 GPIO_ACTIVE_LOW>, <&gpioz 4 GPIO_ACTIVE_LOW>; + spidev: icm20608@0{ + compatible = "invensense,icm20608"; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + interrupt-parent = <&gpioz>; + spi-max-frequency = <8000000>; + reg = <0>; + }; + dac_test: dac_test@1{ + compatible = "100ask,dac"; + spi-max-frequency = <20000000>; + reg = <1>; + }; +}; +``` + + + + + +## 3. 编译替换设备树 + +### 3.1 IMX6ULL + +#### 3.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 +``` + + +#### 3.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 + ``` + +* 重启开发板 + + + +### 3.2 STM32MP157 + +#### 3.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 +``` + + +#### 3.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 + ``` + +* 重启开发板 + + + +## 4. 编译DAC驱动 + + + +## 5. 编译APP + +```shell +arm-buildroot-linux-gnueabihf-gcc -o dac_test dac_test.c +``` + + + +## 6. 上机实验 + +如果spidev没有被编译进内核,那么先执行: + +```shell +insmod dac_drv.ko +``` + + + +确定设备节点: + +```shell +ls /dev/100ask_dac +``` + + + +假设设备节点为`/dev/100ask_dac`,执行测试程序: + +```shell +./dac_test /dev/100ask_dac 500 +./dac_test /dev/100ask_dac 600 +./dac_test /dev/100ask_dac 1000 +``` + + + + + + + + + diff --git a/IMX6ULL/doc_pic/11_SPI/13_编写DAC驱动_上机实验.tif b/IMX6ULL/doc_pic/11_SPI/13_编写DAC驱动_上机实验.tif new file mode 100644 index 0000000..56000f6 Binary files /dev/null and b/IMX6ULL/doc_pic/11_SPI/13_编写DAC驱动_上机实验.tif differ diff --git a/IMX6ULL/doc_pic/11_SPI/14_编写SPI_OLED模块驱动程序.tif b/IMX6ULL/doc_pic/11_SPI/14_编写SPI_OLED模块驱动程序.tif new file mode 100644 index 0000000..56000f6 Binary files /dev/null and b/IMX6ULL/doc_pic/11_SPI/14_编写SPI_OLED模块驱动程序.tif differ diff --git a/IMX6ULL/doc_pic/11_SPI/pic/73_src_dac_drv.png b/IMX6ULL/doc_pic/11_SPI/pic/73_src_dac_drv.png new file mode 100644 index 0000000..b6c7a99 Binary files /dev/null and b/IMX6ULL/doc_pic/11_SPI/pic/73_src_dac_drv.png differ diff --git a/IMX6ULL/source/11_SPI/05_dac_use_mydrv/Makefile b/IMX6ULL/source/11_SPI/05_dac_use_mydrv/Makefile new file mode 100644 index 0000000..1ff40c5 --- /dev/null +++ b/IMX6ULL/source/11_SPI/05_dac_use_mydrv/Makefile @@ -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 dac_test dac_test.c + +clean: + make -C $(KERN_DIR) M=`pwd` modules clean + rm -rf modules.order dac_test + +obj-m += dac_drv.o + + diff --git a/IMX6ULL/source/11_SPI/05_dac_use_mydrv/dac_drv.c b/IMX6ULL/source/11_SPI/05_dac_use_mydrv/dac_drv.c new file mode 100644 index 0000000..58beab1 --- /dev/null +++ b/IMX6ULL/source/11_SPI/05_dac_use_mydrv/dac_drv.c @@ -0,0 +1,174 @@ +/* + * Simple synchronous userspace interface to SPI devices + * + * Copyright (C) 2006 SWAPP + * Andrea Paterniani + * Copyright (C) 2007 David Brownell (simplification, cleanup) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define SPI_IOC_WR 123 + +/*-------------------------------------------------------------------------*/ + +static struct spi_device *dac; +static int major; + +static long +spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int val; + int err; + int old_val = 0; + + struct spi_message msg; + struct spi_transfer xfer; + int status; + + /* copy_from_user */ + err = copy_from_user(&val, (const void __user *)arg, sizeof(int)); + + /* 发起SPI传输: */ + + /* 1. 把val修改为正确的格式 */ + val <<= 2; /* bit0,bit1 = 0b00 */ + val &= 0xFFC; /* 只保留10bit */ + + /* 2. 发起SPI传输同时写\读 */ + /* 2.1 构造transfer + * 2.2 加入message + * 2.3 调用spi_sync + */ + xfer[0].tx_buf = &val; + xfer[0].rx_buf = &old_val; + xfer[0].len = 2; + + spi_message_init(&msg); + spi_message_add_tail(&[0], &msg); + + status = spi_sync(dac, &msg); + + /* 3. 修改读到的数据的格式 */ + old_val >>= 2; + + /* copy_to_user */ + err = copy_to_user((void __user *)arg, &old_val, sizeof(int)); + + return 0; +} + + +static const struct file_operations spidev_fops = { + .owner = THIS_MODULE, + /* REVISIT switch to aio primitives, so that userspace + * gets more complete API coverage. It'll simplify things + * too, except for the locking. + */ + .unlocked_ioctl = spidev_ioctl, +}; + +/*-------------------------------------------------------------------------*/ + +/* The main reason to have this class is to make mdev/udev create the + * /dev/spidevB.C character device nodes exposing our userspace API. + * It also simplifies memory management. + */ + +static struct class *spidev_class; + +static const struct of_device_id spidev_dt_ids[] = { + { .compatible = "100ask,dac" }, + {}, +}; + + +/*-------------------------------------------------------------------------*/ + +static int spidev_probe(struct spi_device *spi) +{ + /* 1. 记录spi_device */ + dac = spi; + + /* 2. 注册字符设备 */ + major = register_chrdev(0, "100ask_dac", &spidev_fops); + spidev_class = class_create(THIS_MODULE, "100ask_dac"); + device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_dac"); + + return 0; +} + +static int spidev_remove(struct spi_device *spi) +{ + /* 反注册字符设备 */ + device_destroy(spidev_class, MKDEV(major, 0)); + class_destroy(spidev_class); + unregister_chrdev(major, "100ask_dac"); + + return 0; +} + +static struct spi_driver spidev_spi_driver = { + .driver = { + .name = "100ask_spi_dac_drv", + .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. + */ +}; + +/*-------------------------------------------------------------------------*/ + +static int __init spidev_init(void) +{ + int status; + + status = spi_register_driver(&spidev_spi_driver); + if (status < 0) { + } + return status; +} +module_init(spidev_init); + +static void __exit spidev_exit(void) +{ + spi_unregister_driver(&spidev_spi_driver); +} +module_exit(spidev_exit); + +MODULE_LICENSE("GPL"); + diff --git a/IMX6ULL/source/11_SPI/05_dac_use_mydrv/dac_test.c b/IMX6ULL/source/11_SPI/05_dac_use_mydrv/dac_test.c new file mode 100644 index 0000000..eca6da0 --- /dev/null +++ b/IMX6ULL/source/11_SPI/05_dac_use_mydrv/dac_test.c @@ -0,0 +1,56 @@ + + +/* 参考: tools\spi\spidev_fdx.c */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define SPI_IOC_WR 123 + +/* dac_test /dev/100ask_dac */ + +int main(int argc, char **argv) +{ + int fd; + unsigned int val; + int status; + + unsigned char tx_buf[2]; + unsigned char rx_buf[2]; + + if (argc != 3) + { + printf("Usage: %s /dev/100ask_dac \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); + + status = ioctl(fd, SPI_IOC_WR, &val); + if (status < 0) { + printf("SPI_IOC_WR\n"); + return -1; + } + + /* 打印 */ + printf("Pre val = %d\n", val); + + + return 0; +} + diff --git a/IMX6ULL/source/11_SPI/06_dac_use_mydrv_ok/Makefile b/IMX6ULL/source/11_SPI/06_dac_use_mydrv_ok/Makefile new file mode 100644 index 0000000..1ff40c5 --- /dev/null +++ b/IMX6ULL/source/11_SPI/06_dac_use_mydrv_ok/Makefile @@ -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 dac_test dac_test.c + +clean: + make -C $(KERN_DIR) M=`pwd` modules clean + rm -rf modules.order dac_test + +obj-m += dac_drv.o + + diff --git a/IMX6ULL/source/11_SPI/06_dac_use_mydrv_ok/dac_drv.c b/IMX6ULL/source/11_SPI/06_dac_use_mydrv_ok/dac_drv.c new file mode 100644 index 0000000..9d75a7b --- /dev/null +++ b/IMX6ULL/source/11_SPI/06_dac_use_mydrv_ok/dac_drv.c @@ -0,0 +1,186 @@ +/* + * Simple synchronous userspace interface to SPI devices + * + * Copyright (C) 2006 SWAPP + * Andrea Paterniani + * Copyright (C) 2007 David Brownell (simplification, cleanup) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define SPI_IOC_WR 123 + +/*-------------------------------------------------------------------------*/ + +static struct spi_device *dac; +static int major; + +static long +spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int val; + int err; + unsigned char tx_buf[2]; + unsigned char rx_buf[2]; + + struct spi_message msg; + struct spi_transfer xfer[1]; + int status; + + memset(&xfer[0], 0, sizeof(xfer)); + + /* copy_from_user */ + err = copy_from_user(&val, (const void __user *)arg, sizeof(int)); + + printk("spidev_ioctl get val from user: %d\n", val); + + /* 发起SPI传输: */ + + /* 1. 把val修改为正确的格式 */ + val <<= 2; /* bit0,bit1 = 0b00 */ + val &= 0xFFC; /* 只保留10bit */ + + tx_buf[1] = val & 0xff; + tx_buf[0] = (val>>8) & 0xff; + + /* 2. 发起SPI传输同时写\读 */ + /* 2.1 构造transfer + * 2.2 加入message + * 2.3 调用spi_sync + */ + xfer[0].tx_buf = tx_buf; + xfer[0].rx_buf = rx_buf; + xfer[0].len = 2; + + spi_message_init(&msg); + spi_message_add_tail(&xfer[0], &msg); + + status = spi_sync(dac, &msg); + + /* 3. 修改读到的数据的格式 */ + val = (rx_buf[0] << 8) | (rx_buf[1]); + val >>= 2; + + /* copy_to_user */ + err = copy_to_user((void __user *)arg, &val, sizeof(int)); + + return 0; +} + + +static const struct file_operations spidev_fops = { + .owner = THIS_MODULE, + /* REVISIT switch to aio primitives, so that userspace + * gets more complete API coverage. It'll simplify things + * too, except for the locking. + */ + .unlocked_ioctl = spidev_ioctl, +}; + +/*-------------------------------------------------------------------------*/ + +/* The main reason to have this class is to make mdev/udev create the + * /dev/spidevB.C character device nodes exposing our userspace API. + * It also simplifies memory management. + */ + +static struct class *spidev_class; + +static const struct of_device_id spidev_dt_ids[] = { + { .compatible = "100ask,dac" }, + {}, +}; + + +/*-------------------------------------------------------------------------*/ + +static int spidev_probe(struct spi_device *spi) +{ + /* 1. 记录spi_device */ + dac = spi; + + /* 2. 注册字符设备 */ + major = register_chrdev(0, "100ask_dac", &spidev_fops); + spidev_class = class_create(THIS_MODULE, "100ask_dac"); + device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_dac"); + + return 0; +} + +static int spidev_remove(struct spi_device *spi) +{ + /* 反注册字符设备 */ + device_destroy(spidev_class, MKDEV(major, 0)); + class_destroy(spidev_class); + unregister_chrdev(major, "100ask_dac"); + + return 0; +} + +static struct spi_driver spidev_spi_driver = { + .driver = { + .name = "100ask_spi_dac_drv", + .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. + */ +}; + +/*-------------------------------------------------------------------------*/ + +static int __init spidev_init(void) +{ + int status; + + printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); + + status = spi_register_driver(&spidev_spi_driver); + if (status < 0) { + } + return status; +} +module_init(spidev_init); + +static void __exit spidev_exit(void) +{ + printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); + spi_unregister_driver(&spidev_spi_driver); +} +module_exit(spidev_exit); + +MODULE_LICENSE("GPL"); + diff --git a/IMX6ULL/source/11_SPI/06_dac_use_mydrv_ok/dac_test.c b/IMX6ULL/source/11_SPI/06_dac_use_mydrv_ok/dac_test.c new file mode 100644 index 0000000..eca6da0 --- /dev/null +++ b/IMX6ULL/source/11_SPI/06_dac_use_mydrv_ok/dac_test.c @@ -0,0 +1,56 @@ + + +/* 参考: tools\spi\spidev_fdx.c */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define SPI_IOC_WR 123 + +/* dac_test /dev/100ask_dac */ + +int main(int argc, char **argv) +{ + int fd; + unsigned int val; + int status; + + unsigned char tx_buf[2]; + unsigned char rx_buf[2]; + + if (argc != 3) + { + printf("Usage: %s /dev/100ask_dac \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); + + status = ioctl(fd, SPI_IOC_WR, &val); + if (status < 0) { + printf("SPI_IOC_WR\n"); + return -1; + } + + /* 打印 */ + printf("Pre val = %d\n", val); + + + return 0; +} + diff --git a/README.md b/README.md index 060ecfc..4fc36ba 100644 --- a/README.md +++ b/README.md @@ -545,13 +545,18 @@ git clone https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git ``` * 2021.04.25 发布"SPI子系统": - + ```shell 10_OLED模块上机实验_STM32MP157 ``` +* 2021.04.26 发布"SPI子系统": - + ```shell + 12_编写SPI_DAC模块驱动程序 + 13_编写DAC驱动_上机实验_IMX6ULL + 13_编写DAC驱动_上机实验_STM32MP157 + ``` diff --git a/STM32MP157/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.md b/STM32MP157/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.md index 6e47855..4bc299f 100644 --- a/STM32MP157/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.md +++ b/STM32MP157/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.md @@ -4,12 +4,25 @@ * DAC芯片手册:`TLC5615.pdf` - -## 1. 硬件 -### 1.1 原理图 +## 1. 要做什么事情 + +![](pic/09_spi_drv_frame.png) + + + +* 查看原理图,编写设备树 +* 编写驱动程序,注册一个spidrv +* 编写测试程序 + + + + +## 2. 硬件 + +### 2.1 原理图 IMX6ULL: @@ -27,9 +40,9 @@ STM32MP157: -### 1.2 连接 +### 2.2 连接 -#### 1.2.1 IMX6ULL +#### 2.2.1 IMX6ULL DAC模块接到IMX6ULL扩展板的SPI_A插座上: @@ -37,11 +50,11 @@ DAC模块接到IMX6ULL扩展板的SPI_A插座上: -#### 1.2.2 STM32MP157 +#### 2.2.2 STM32MP157 -## 2. 编写设备树 +## 3. 编写设备树 确认SPI时钟最大频率: @@ -66,7 +79,7 @@ F = 20000000 = 20MHz -### 2.1 IMX6ULL +### 3.1 IMX6ULL ![image-20220311101017666](pic/44_imx6ull_pro_extend_spi_a.png) @@ -95,7 +108,7 @@ DAC模块接在这个插座上,那么要在设备树里spi1的节点下创建 -### 2.2 STM32MP157 +### 3.2 STM32MP157 ![image-20220311101127305](pic/45_stm32mp157_pro_extend_spi_a.png) @@ -129,7 +142,7 @@ DAC模块接在这个插座上,那么要在设备树里spi5的节点下创建 -## 3. 编写驱动程序 +## 4. 编写驱动程序 以前我们基于spidev编写过DAC的应用程序,可以参考它: @@ -139,5 +152,5 @@ DAC模块接在这个插座上,那么要在设备树里spi5的节点下创建 -## 4. 编写测试程序 +## 5. 编写测试程序 diff --git a/STM32MP157/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.tif b/STM32MP157/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.tif index d28a9c2..56000f6 100644 Binary files a/STM32MP157/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.tif and b/STM32MP157/doc_pic/11_SPI/12_编写SPI_DAC模块驱动程序.tif differ diff --git a/STM32MP157/doc_pic/11_SPI/13_编写DAC驱动_上机实验.md b/STM32MP157/doc_pic/11_SPI/13_编写DAC驱动_上机实验.md new file mode 100644 index 0000000..10c1161 --- /dev/null +++ b/STM32MP157/doc_pic/11_SPI/13_编写DAC驱动_上机实验.md @@ -0,0 +1,286 @@ +# 编写DAC驱动_上机实验 # + +* 源码 + +![image-20220426095853618](pic/73_src_dac_drv.png) + + + +## 1. 硬件 + +### 1.1 原理图 + +IMX6ULL: + +![image-20220309150927785](pic/33_imx6ull_dac.png) + +STM32MP157: + +![image-20220309151025637](pic/34_stm32mp157_dac.png) + + + +原理图: + +![image-20220309151636533](pic/35_dac_sch.png) + + + +### 1.2 连接 + +#### 1.2.1 IMX6ULL + +DAC模块接到IMX6ULL扩展板的SPI_A插座上: + +![image-20220309164031109](pic/40_dac_on_imx6ull.png) + + + +#### 1.2.2 STM32MP157 + +DAC模块接到STM32MP157扩展板的SPI_A插座上: + +![image-20220311100618477](pic/43_dac_on_stm32mp157.png) + + + + + +## 2. 编写设备树 + +确认SPI时钟最大频率: + +![image-20220309163435541](pic/39_dac_time_param.png) + +```shell +T = 25 + 25 = 50ns +F = 20000000 = 20MHz +``` + + + +设备树如下: + +```shell + dac: dac { + compatible = "100ask,dac"; + reg = <0>; + spi-max-frequency = <20000000>; + }; +``` + + + +### 2.1 IMX6ULL + +![image-20220311101017666](pic/44_imx6ull_pro_extend_spi_a.png) + +DAC模块接在这个插座上,那么要在设备树里spi1的节点下创建子节点。 + +代码在`arch/arm/boot/dts/100ask_imx6ull-14x14.dts`中,如下: + +```shell +&ecspi1 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_ecspi1>; + + fsl,spi-num-chipselects = <2>; + cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>; + status = "okay"; + + dac: dac { + compatible = "100ask,dac"; + reg = <0>; + spi-max-frequency = <20000000>; + }; +}; +``` + + + + + +### 2.2 STM32MP157 + +![image-20220311101127305](pic/45_stm32mp157_pro_extend_spi_a.png) + +DAC模块接在这个插座上,那么要在设备树里spi5的节点下创建子节点。 + +代码在`arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dts`中,如下: + +```shell +&spi5 { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&spi5_pins_a>; + pinctrl-1 = <&spi5_sleep_pins_a>; + status = "okay"; + cs-gpios = <&gpioh 5 GPIO_ACTIVE_LOW>, <&gpioz 4 GPIO_ACTIVE_LOW>; + spidev: icm20608@0{ + compatible = "invensense,icm20608"; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + interrupt-parent = <&gpioz>; + spi-max-frequency = <8000000>; + reg = <0>; + }; + dac_test: dac_test@1{ + compatible = "100ask,dac"; + spi-max-frequency = <20000000>; + reg = <1>; + }; +}; +``` + + + + + +## 3. 编译替换设备树 + +### 3.1 IMX6ULL + +#### 3.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 +``` + + +#### 3.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 + ``` + +* 重启开发板 + + + +### 3.2 STM32MP157 + +#### 3.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 +``` + + +#### 3.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 + ``` + +* 重启开发板 + + + +## 4. 编译DAC驱动 + + + +## 5. 编译APP + +```shell +arm-buildroot-linux-gnueabihf-gcc -o dac_test dac_test.c +``` + + + +## 6. 上机实验 + +如果spidev没有被编译进内核,那么先执行: + +```shell +insmod dac_drv.ko +``` + + + +确定设备节点: + +```shell +ls /dev/100ask_dac +``` + + + +假设设备节点为`/dev/100ask_dac`,执行测试程序: + +```shell +./dac_test /dev/100ask_dac 500 +./dac_test /dev/100ask_dac 600 +./dac_test /dev/100ask_dac 1000 +``` + + + + + + + + + diff --git a/STM32MP157/doc_pic/11_SPI/13_编写DAC驱动_上机实验.tif b/STM32MP157/doc_pic/11_SPI/13_编写DAC驱动_上机实验.tif new file mode 100644 index 0000000..56000f6 Binary files /dev/null and b/STM32MP157/doc_pic/11_SPI/13_编写DAC驱动_上机实验.tif differ diff --git a/STM32MP157/doc_pic/11_SPI/14_编写SPI_OLED模块驱动程序.tif b/STM32MP157/doc_pic/11_SPI/14_编写SPI_OLED模块驱动程序.tif new file mode 100644 index 0000000..56000f6 Binary files /dev/null and b/STM32MP157/doc_pic/11_SPI/14_编写SPI_OLED模块驱动程序.tif differ diff --git a/STM32MP157/doc_pic/11_SPI/pic/73_src_dac_drv.png b/STM32MP157/doc_pic/11_SPI/pic/73_src_dac_drv.png new file mode 100644 index 0000000..b6c7a99 Binary files /dev/null and b/STM32MP157/doc_pic/11_SPI/pic/73_src_dac_drv.png differ diff --git a/STM32MP157/source/A7/11_SPI/05_dac_use_mydrv/Makefile b/STM32MP157/source/A7/11_SPI/05_dac_use_mydrv/Makefile new file mode 100644 index 0000000..03c9e99 --- /dev/null +++ b/STM32MP157/source/A7/11_SPI/05_dac_use_mydrv/Makefile @@ -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 dac_test dac_test.c + +clean: + make -C $(KERN_DIR) M=`pwd` modules clean + rm -rf modules.order dac_test + +obj-m += dac_drv.o + + diff --git a/STM32MP157/source/A7/11_SPI/05_dac_use_mydrv/dac_drv.c b/STM32MP157/source/A7/11_SPI/05_dac_use_mydrv/dac_drv.c new file mode 100644 index 0000000..58beab1 --- /dev/null +++ b/STM32MP157/source/A7/11_SPI/05_dac_use_mydrv/dac_drv.c @@ -0,0 +1,174 @@ +/* + * Simple synchronous userspace interface to SPI devices + * + * Copyright (C) 2006 SWAPP + * Andrea Paterniani + * Copyright (C) 2007 David Brownell (simplification, cleanup) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define SPI_IOC_WR 123 + +/*-------------------------------------------------------------------------*/ + +static struct spi_device *dac; +static int major; + +static long +spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int val; + int err; + int old_val = 0; + + struct spi_message msg; + struct spi_transfer xfer; + int status; + + /* copy_from_user */ + err = copy_from_user(&val, (const void __user *)arg, sizeof(int)); + + /* 发起SPI传输: */ + + /* 1. 把val修改为正确的格式 */ + val <<= 2; /* bit0,bit1 = 0b00 */ + val &= 0xFFC; /* 只保留10bit */ + + /* 2. 发起SPI传输同时写\读 */ + /* 2.1 构造transfer + * 2.2 加入message + * 2.3 调用spi_sync + */ + xfer[0].tx_buf = &val; + xfer[0].rx_buf = &old_val; + xfer[0].len = 2; + + spi_message_init(&msg); + spi_message_add_tail(&[0], &msg); + + status = spi_sync(dac, &msg); + + /* 3. 修改读到的数据的格式 */ + old_val >>= 2; + + /* copy_to_user */ + err = copy_to_user((void __user *)arg, &old_val, sizeof(int)); + + return 0; +} + + +static const struct file_operations spidev_fops = { + .owner = THIS_MODULE, + /* REVISIT switch to aio primitives, so that userspace + * gets more complete API coverage. It'll simplify things + * too, except for the locking. + */ + .unlocked_ioctl = spidev_ioctl, +}; + +/*-------------------------------------------------------------------------*/ + +/* The main reason to have this class is to make mdev/udev create the + * /dev/spidevB.C character device nodes exposing our userspace API. + * It also simplifies memory management. + */ + +static struct class *spidev_class; + +static const struct of_device_id spidev_dt_ids[] = { + { .compatible = "100ask,dac" }, + {}, +}; + + +/*-------------------------------------------------------------------------*/ + +static int spidev_probe(struct spi_device *spi) +{ + /* 1. 记录spi_device */ + dac = spi; + + /* 2. 注册字符设备 */ + major = register_chrdev(0, "100ask_dac", &spidev_fops); + spidev_class = class_create(THIS_MODULE, "100ask_dac"); + device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_dac"); + + return 0; +} + +static int spidev_remove(struct spi_device *spi) +{ + /* 反注册字符设备 */ + device_destroy(spidev_class, MKDEV(major, 0)); + class_destroy(spidev_class); + unregister_chrdev(major, "100ask_dac"); + + return 0; +} + +static struct spi_driver spidev_spi_driver = { + .driver = { + .name = "100ask_spi_dac_drv", + .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. + */ +}; + +/*-------------------------------------------------------------------------*/ + +static int __init spidev_init(void) +{ + int status; + + status = spi_register_driver(&spidev_spi_driver); + if (status < 0) { + } + return status; +} +module_init(spidev_init); + +static void __exit spidev_exit(void) +{ + spi_unregister_driver(&spidev_spi_driver); +} +module_exit(spidev_exit); + +MODULE_LICENSE("GPL"); + diff --git a/STM32MP157/source/A7/11_SPI/05_dac_use_mydrv/dac_test.c b/STM32MP157/source/A7/11_SPI/05_dac_use_mydrv/dac_test.c new file mode 100644 index 0000000..eca6da0 --- /dev/null +++ b/STM32MP157/source/A7/11_SPI/05_dac_use_mydrv/dac_test.c @@ -0,0 +1,56 @@ + + +/* 参考: tools\spi\spidev_fdx.c */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define SPI_IOC_WR 123 + +/* dac_test /dev/100ask_dac */ + +int main(int argc, char **argv) +{ + int fd; + unsigned int val; + int status; + + unsigned char tx_buf[2]; + unsigned char rx_buf[2]; + + if (argc != 3) + { + printf("Usage: %s /dev/100ask_dac \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); + + status = ioctl(fd, SPI_IOC_WR, &val); + if (status < 0) { + printf("SPI_IOC_WR\n"); + return -1; + } + + /* 打印 */ + printf("Pre val = %d\n", val); + + + return 0; +} + diff --git a/STM32MP157/source/A7/11_SPI/06_dac_use_mydrv_ok/Makefile b/STM32MP157/source/A7/11_SPI/06_dac_use_mydrv_ok/Makefile new file mode 100644 index 0000000..03c9e99 --- /dev/null +++ b/STM32MP157/source/A7/11_SPI/06_dac_use_mydrv_ok/Makefile @@ -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 dac_test dac_test.c + +clean: + make -C $(KERN_DIR) M=`pwd` modules clean + rm -rf modules.order dac_test + +obj-m += dac_drv.o + + diff --git a/STM32MP157/source/A7/11_SPI/06_dac_use_mydrv_ok/dac_drv.c b/STM32MP157/source/A7/11_SPI/06_dac_use_mydrv_ok/dac_drv.c new file mode 100644 index 0000000..9d75a7b --- /dev/null +++ b/STM32MP157/source/A7/11_SPI/06_dac_use_mydrv_ok/dac_drv.c @@ -0,0 +1,186 @@ +/* + * Simple synchronous userspace interface to SPI devices + * + * Copyright (C) 2006 SWAPP + * Andrea Paterniani + * Copyright (C) 2007 David Brownell (simplification, cleanup) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define SPI_IOC_WR 123 + +/*-------------------------------------------------------------------------*/ + +static struct spi_device *dac; +static int major; + +static long +spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int val; + int err; + unsigned char tx_buf[2]; + unsigned char rx_buf[2]; + + struct spi_message msg; + struct spi_transfer xfer[1]; + int status; + + memset(&xfer[0], 0, sizeof(xfer)); + + /* copy_from_user */ + err = copy_from_user(&val, (const void __user *)arg, sizeof(int)); + + printk("spidev_ioctl get val from user: %d\n", val); + + /* 发起SPI传输: */ + + /* 1. 把val修改为正确的格式 */ + val <<= 2; /* bit0,bit1 = 0b00 */ + val &= 0xFFC; /* 只保留10bit */ + + tx_buf[1] = val & 0xff; + tx_buf[0] = (val>>8) & 0xff; + + /* 2. 发起SPI传输同时写\读 */ + /* 2.1 构造transfer + * 2.2 加入message + * 2.3 调用spi_sync + */ + xfer[0].tx_buf = tx_buf; + xfer[0].rx_buf = rx_buf; + xfer[0].len = 2; + + spi_message_init(&msg); + spi_message_add_tail(&xfer[0], &msg); + + status = spi_sync(dac, &msg); + + /* 3. 修改读到的数据的格式 */ + val = (rx_buf[0] << 8) | (rx_buf[1]); + val >>= 2; + + /* copy_to_user */ + err = copy_to_user((void __user *)arg, &val, sizeof(int)); + + return 0; +} + + +static const struct file_operations spidev_fops = { + .owner = THIS_MODULE, + /* REVISIT switch to aio primitives, so that userspace + * gets more complete API coverage. It'll simplify things + * too, except for the locking. + */ + .unlocked_ioctl = spidev_ioctl, +}; + +/*-------------------------------------------------------------------------*/ + +/* The main reason to have this class is to make mdev/udev create the + * /dev/spidevB.C character device nodes exposing our userspace API. + * It also simplifies memory management. + */ + +static struct class *spidev_class; + +static const struct of_device_id spidev_dt_ids[] = { + { .compatible = "100ask,dac" }, + {}, +}; + + +/*-------------------------------------------------------------------------*/ + +static int spidev_probe(struct spi_device *spi) +{ + /* 1. 记录spi_device */ + dac = spi; + + /* 2. 注册字符设备 */ + major = register_chrdev(0, "100ask_dac", &spidev_fops); + spidev_class = class_create(THIS_MODULE, "100ask_dac"); + device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_dac"); + + return 0; +} + +static int spidev_remove(struct spi_device *spi) +{ + /* 反注册字符设备 */ + device_destroy(spidev_class, MKDEV(major, 0)); + class_destroy(spidev_class); + unregister_chrdev(major, "100ask_dac"); + + return 0; +} + +static struct spi_driver spidev_spi_driver = { + .driver = { + .name = "100ask_spi_dac_drv", + .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. + */ +}; + +/*-------------------------------------------------------------------------*/ + +static int __init spidev_init(void) +{ + int status; + + printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); + + status = spi_register_driver(&spidev_spi_driver); + if (status < 0) { + } + return status; +} +module_init(spidev_init); + +static void __exit spidev_exit(void) +{ + printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); + spi_unregister_driver(&spidev_spi_driver); +} +module_exit(spidev_exit); + +MODULE_LICENSE("GPL"); + diff --git a/STM32MP157/source/A7/11_SPI/06_dac_use_mydrv_ok/dac_test.c b/STM32MP157/source/A7/11_SPI/06_dac_use_mydrv_ok/dac_test.c new file mode 100644 index 0000000..eca6da0 --- /dev/null +++ b/STM32MP157/source/A7/11_SPI/06_dac_use_mydrv_ok/dac_test.c @@ -0,0 +1,56 @@ + + +/* 参考: tools\spi\spidev_fdx.c */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define SPI_IOC_WR 123 + +/* dac_test /dev/100ask_dac */ + +int main(int argc, char **argv) +{ + int fd; + unsigned int val; + int status; + + unsigned char tx_buf[2]; + unsigned char rx_buf[2]; + + if (argc != 3) + { + printf("Usage: %s /dev/100ask_dac \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); + + status = ioctl(fd, SPI_IOC_WR, &val); + if (status < 0) { + printf("SPI_IOC_WR\n"); + return -1; + } + + /* 打印 */ + printf("Pre val = %d\n", val); + + + return 0; +} +