文章目录
- 参考资料:
- 一、编写设备树
- 二、 编写驱动程序
- 三、编写测试APP
- 四、Makefile
- 五、上机实验
参考资料:
参考资料:
- 内核头文件:
include\linux\spi\spi.h
- 内核文档:
Documentation\spi\spidev
- DAC芯片手册:
TLC5615.pdf
一、编写设备树
确认最大时钟频率:参看芯片手册
T = 25 + 25 = 50ns
F = 20000000 = 20MHz
修改\arch\arm\boot\dts\100ask_imx6ull-14x14.dts
&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>;
};
};
二、 编写驱动程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include <linux/uaccess.h>
#define SPI_IOC_WR 123
/*-------------------------------------------------------------------------*/
static struct spi_device *g_spidev_dac;
static int g_major;
static struct class *g_spidev_class;
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;
/* 局部变量初始值未知,需清0 */
memset(&xfer[0], 0, sizeof(xfer));
/* 从用户层获取数据 */
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; //低8位
tx_buf[0] = (val>>8) & 0xff; //高8位
/* 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(g_spidev_dac, &msg);
/* 3. 修改读到的数据的格式 */
val = (rx_buf[0] << 8) | (rx_buf[1]);
val >>= 2;
/* 返回给用户层 */
err = copy_to_user((void __user *)arg, &val, sizeof(int));
return 0;
}
/* file_operations 结构 */
static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = spidev_ioctl,
};
/*-------------------------------------------------------------------------*/
static int spidev_probe(struct spi_device *spi)
{
/* 1. 记录spi_device */
g_spidev_dac = spi;
/* 2. 注册字符设备 */
g_major = register_chrdev(0, "100ask_dac", &spidev_fops);
g_spidev_class = class_create(THIS_MODULE, "100ask_dac");
device_create(g_spidev_class, NULL, MKDEV(g_major, 0), NULL, "100ask_dac");
return 0;
}
static int spidev_remove(struct spi_device *spi)
{
/* 反注册字符设备 */
device_destroy(g_spidev_class, MKDEV(g_major, 0));
class_destroy(g_spidev_class);
unregister_chrdev(g_major, "100ask_dac");
return 0;
}
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "100ask,dac" },
{},
};
/* 构造一个spi_driver */
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,
};
/* 入口函数 */
static int __init spidev_init(void)
{
int status;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 注册spi_driver */
status = spi_register_driver(&spidev_spi_driver);
if (status < 0) {
}
return status;
}
/* 出口函数 */
static void __exit spidev_exit(void)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 反注册spi_driver */
spi_unregister_driver(&spidev_spi_driver);
}
module_init(spidev_init);
module_exit(spidev_exit);
MODULE_LICENSE("GPL");
三、编写测试APP
参考: 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>
#define SPI_IOC_WR 123
/* dac_test /dev/100ask_dac <val> */
int main(int argc, char **argv)
{
int fd;
unsigned int val;
int status;
if (argc != 3)
{
printf("Usage: %s /dev/100ask_dac <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);
/* 写入数据,并读回数据 */
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;
}
四、Makefile
KERN_DIR = /home/zpz/share/imx6ullsdk/repo/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
五、上机实验
- 编译、替换设备树
- 编译设备树
$ make dtbs
- 拷贝到开发板
$ mount -t nfs -o nolock 192.168.124.22:/home/zpz/share/ /mnt
$ cp /mnt/imx6ullsdk/repo/100ask_imx6ull-sdk/Linux-4.9.88/arch/arm/boot/dts/100ask_imx6ull-14x14.dtb /boot/
- 重启开发板
-
编译驱动和测试程序
-
加载驱动
$ insmod dac_drv.ko
- 查看设备节点
$ ls /dev/100ask_dac -l
crw------- 1 root root 240, 0 Jan 1 01:47 /dev/100ask_dac
- 运行测试程序
//传入不用val,亮度不同
$ ./dac_test /dev/100ask_dac 100
Pre val = 0
$ ./dac_test /dev/100ask_dac 500
Pre val = 100
$ ./dac_test /dev/100ask_dac 1000
Pre val = 500