在Linux系统(ubuntu18.04)下,基于设备树(device tree)而不是platform总线(驱动开发分platform总线与设备树),编写SPI驱动
随着工作的需要,提升底层技术也越来越重要,很多人都开启了驱动学习,本文就介绍了SPI驱动学习的基础内容。
总体分为两个步骤:修改设备树 + 编写驱动
用来描述硬件信息,硬件有哪些资源。
驱动开发分platform总线与设备树,本文在这里介绍的是基于设备树的spi驱动编写方法,那么可能就有人要问了,这两种驱动开发的有什么区别?
我们知道:Linux驱动 = 驱动框架 + 硬件操作
在platform总线下,需要编写指定硬件描述信息(platform_device)的资源文件xxx_dev.c,编写驱动程序(platform_driver)的驱动文件xxx_drv.c
在设备树下,资源用设备树指定,只需要编写驱动程序的驱动文件xxx_drv.c
简而言之,设备树文件包含了硬件的描述信息。在设备树情况下:不需要一行行写硬件的描述信息即资源文件xxx_dev.c,需要的硬件信息在设备树文件里面找,传说Linux之父因嫌xxx_dev.c文件太多导致Linux臃肿而引入了DTS即由DeviceTree机制来负责传递硬件拓扑和硬件资源信息。
通过模仿已经写好可以用的,对比我们自己的设备信息来修改
/dts-v1/;
#include
#include "xxx.dtsi"
/ {
xxxxxx
xxxxxx
xxx{
xxxxxx
......
}
......
&xxxspix {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_xxxspix>;
fsl,spi-num-chipselects = <2>;
cs-gpios = <&gpiox xx GPIO_ACTIVE_LOW>, <&gpiox xx GPIO_ACTIVE_LOW>;
status = "okay";
spidev0: spi@0 {
compatible = "rohm,dh2228fv";
reg = <0>;
spi-max-frequency = <5000000>;
};
spidev1: spi@1 {
compatible = "rohm,dh2228fv";
reg = <1>;
spi-max-frequency = <5000000>;
};
};
/dts-v1/;
#include
#include "xxx.dtsi"
/ { /* 最外面的大括号是根节点,我们需要在根节点下里面写子节点,第二层大括号中编写 */
xxxxxx
xxxxxx
xxx{
xxxxxx
......
}
......
&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"; /* 使用子节点固定要写为okay */
a5: a5@0 {
compatible = "veb,a5"; /* dev 和 drv 匹配的关键点:name要改写得一样 */
reg = <0>;
spi-max-frequency = <2000000>; /* 根据spi设备的传输频率改写 */
};
};
写spi驱动之前想必已经入门简单驱动编写,像hello驱动和led驱动这种类型的驱动编写应该是小菜一碟。繁琐的话不多说,具体spi驱动代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "veba5_reg.h"
#define VEBA5_CNT 1
#define VEBA5_NAME "a5"
struct veba5_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int major; /* 主设备号 */
void *private_data; /* 私有数据 */
};
static struct veba5_dev veba5dev;
void printmem( const void* mem, size_t size )
{
int i;
printk( "{");
for( i=0; i!=size; ++i )
printk( "%02hhX", ((const unsigned char*)mem)[i] );
printk( "}\n" );
}
static int a5_spi_write(struct veba5_dev *dev, char *cmd, int len)
{
struct spi_device *spi = (struct spi_device *)dev->private_data;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
printmem(cmd, DATALEN);
spi_write(spi, cmd, DATALEN); /* 写传输:系统自带的,是下面几行代码的封装 */
//t[0].tx_buf = cmd; /* 要发送的数据 */
//t[0].len = CMD_T_SIZE; /* t->len=发送的长度+读取的长度 */
//spi_message_init(&m); /* 初始化spi_message */
//spi_message_add_tail(&t[0], &m);/* 将spi_transfer添加到spi_message队列 */
//spi_sync(spi, &m); /* 同步发送 */
printk("%s %s %d OUT\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static long veba5_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int err;
struct veba5_dev *dev = (struct veba5_dev *)filp->private_data;
//char data[DATALEN] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x80, 0x40, 0x20, 0x10, 0xf0};
char tmp_data[DATALEN] = {0};
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* data 和应用层传下来的数据一样,也可以换成结构体 */
err = copy_from_user(&tmp_data, (const void __user *)arg, DATALEN);
switch (cmd)
{
case CMD_INS:
{
a5_spi_write(dev, tmp_data, DATALEN);
break;
}
default:printk("veba5_ioctl cmd error\n");
}
printk("%s %s %d veba5_ioctl OUT\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做pr似有ate_data的成员变量
* 一般在open的时候将private_data似有向设备结构体。
* @return : 0 成功;其他 失败
*/
static int veba5_open(struct inode *inode, struct file *filp)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
filp->private_data = &veba5dev; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t veba5_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
//struct veba5_dev *dev = (struct veba5_dev *)filp->private_data;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
//err = copy_to_user(buf, data, sizeof(data));
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int veba5_close(struct inode *inode, struct file *filp)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/* veba5操作函数 */
static struct file_operations veba5_ops = {
.owner = THIS_MODULE,
.open = veba5_open,
.read = veba5_read,
.release = veba5_close,
.unlocked_ioctl = veba5_ioctl,
};
/*
* 内部寄存器初始化函数
* @param : 无
* @return : 无
*/
void veba5_reginit(void)
{
unsigned int val;
//unsigned int i;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
CCM_CCGR3 = ioremap(0x020C4074, 4);
IOMUXC_SW_MUX_CTL_PAD_CSI_DATA00 = ioremap(0x020E01E4, 4);
IOMUXC_SW_MUX_CTL_PAD_CSI_DATA01 = ioremap(0x020E01E8, 4);
// IOMUXC_SW_MUX_CTL_PAD_CSI_DATA02 = ioremap(0x020E01EC, 4);
GPIO4_GDIR = ioremap(GPIO4_BASE_ADD + 0x4, 4);
GPIO4_DR = ioremap(GPIO4_BASE_ADD + 0, 4);
printk("iomap\n\r");
/* a. 使能GPIO4 */
*CCM_CCGR3 |= (3<<13);
/* b. 设置GPIO4_IO21-23用于GPIO */
val = *IOMUXC_SW_MUX_CTL_PAD_CSI_DATA00;
val &= ~(0xf);
val |= (5);
*IOMUXC_SW_MUX_CTL_PAD_CSI_DATA00 = val;
val = *IOMUXC_SW_MUX_CTL_PAD_CSI_DATA01;
val &= ~(0xf);
val |= (5);
*IOMUXC_SW_MUX_CTL_PAD_CSI_DATA01 = val;
/*
val = *IOMUXC_SW_MUX_CTL_PAD_CSI_DATA02;
val &= ~(0xf);
val |= (5);
*IOMUXC_SW_MUX_CTL_PAD_CSI_DATA02 = val;
*/
/* c. 设置GPIO4_IO21-22作为output引脚 */
*GPIO4_GDIR &= ~(1<<21);/* 设置GPIO4_IO21 input引脚, 准备读 */
*GPIO4_GDIR |= (1<<22); /* 设置GPIO4_IO22 output引脚, 准备写 */
/* 配置个引脚 */
*GPIO4_DR |= (1<<22);/* 设置GPIO4_IO22 , 默认高电平 */
}
/*
* @description : spi驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : spi设备
* @param - id : spi设备ID
*
*/
static int veba5_probe(struct spi_device *spi)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 1、构建设备号 */
if (veba5dev.major) {
veba5dev.devid = MKDEV(veba5dev.major, 0);
register_chrdev_region(veba5dev.devid, VEBA5_CNT, VEBA5_NAME);
} else {
alloc_chrdev_region(&veba5dev.devid, 0, VEBA5_CNT, VEBA5_NAME);
veba5dev.major = MAJOR(veba5dev.devid);
}
/* 2、注册设备 */
cdev_init(&veba5dev.cdev, &veba5_ops);
cdev_add(&veba5dev.cdev, veba5dev.devid, VEBA5_CNT);
/* 3、创建类 */
veba5dev.class = class_create(THIS_MODULE, VEBA5_NAME);
if (IS_ERR(veba5dev.class)) {
return PTR_ERR(veba5dev.class);
}
/* 4、创建设备 */
veba5dev.device = device_create(veba5dev.class, NULL, veba5dev.devid, NULL, VEBA5_NAME);
if (IS_ERR(veba5dev.device)) {
return PTR_ERR(veba5dev.device);
}
/*初始化spi_device */
spi->mode = SPI_MODE_0;// | SPI_LSB_FIRST; /*MODE0,CPOL=0,CPHA=0*/
spi_setup(spi);
veba5dev.private_data = spi; /* 设置私有数据 */
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 初始化veba5内部寄存器 */
veba5_reginit();
return 0;
}
/*
* @description : spi驱动的remove函数,移除spi驱动的时候此函数会执行
* @param - client : spi设备
* @return : 0,成功;其他负值,失败
*/
static int veba5_remove(struct spi_device *spi)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 删除设备 */
cdev_del(&veba5dev.cdev);
unregister_chrdev_region(veba5dev.devid, VEBA5_CNT);
/* 注销掉类和设备 */
device_destroy(veba5dev.class, veba5dev.devid);
class_destroy(veba5dev.class);
return 0;
}
///* 传统匹配方式ID列表 */
//static const struct spi_device_id icm20608_id[] = {
// {"100ask,icm20608", 0},
// {}
//};
/* 设备树匹配列表 */
static const struct of_device_id veba5_of_match[] = {
{ .compatible = "veb,a5" },
{ },
};
/* SPI驱动结构体 */
static struct spi_driver veba5_driver = {
.probe = veba5_probe,
.remove = veba5_remove,
.driver = {
//.owner = THIS_MODULE,
.name = "a5",
.of_match_table = veba5_of_match,
},
//.id_table = icm20608_id,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init veba5_init(void)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return spi_register_driver(&veba5_driver);
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit veba5_exit(void)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
spi_unregister_driver(&veba5_driver);
iounmap(CCM_CCGR3);
iounmap(IOMUXC_SW_MUX_CTL_PAD_CSI_DATA00);
iounmap(IOMUXC_SW_MUX_CTL_PAD_CSI_DATA01);
//iounmap(IOMUXC_SW_MUX_CTL_PAD_CSI_DATA02);
iounmap(GPIO4_GDIR);
iounmap(GPIO4_DR);
printk("iounmap\n\r");
}
module_init(veba5_init);
module_exit(veba5_exit);
MODULE_LICENSE("GPL");
Linux设备树SPI驱动的打包文件下载跳转
make
cp veba5_drv.ko veba5_app ~/nfs_rootfs/xxx_driver/
insmod veba5drv.ko
./veba5_app /dev/a5
还有一些问题可供思考,欢迎留言评论,我们一起来探讨
这里对文章进行总结:
以上就是今天要讲的内容,本文仅仅简单介绍了Linux下SPI驱动的编写,而Linux提供了大量能使我们快速便捷地处理数据的函数和方法。
以上就是本篇博文的全部内容,如果本篇博文对您有帮助,或者有所启发,动动小手为我点个赞,让我知道我写的东西并不是一无是处,还是有那么一点作用滴!of course,我也会继续努力,写出更好的文章!!!