您现在的位置是:首页 >技术交流 >信息x腹稿网站首页技术交流
信息x腹稿
1、自我介绍
你好,我叫,是就读于电子信息专业的24届研究生。在校期间获得过两次一等奖学金、两次省级竞赛一等奖,英语过了6级,我的研究方向是水下slam多传感器融合方向,用过c/c++/python三种编程语言。
2、系统移植项目
这个项目是将一块imx6ull系列板子移植上系统。我移植参考的是NXP的i.MX6ULL评估板。我去uboot官网上下载2016.03版本的代码,内核是4.15、buildroot构建根文件系统。将板子原理图与NXP原理图对比 发现区别是在与屏幕、网络接口phy芯片款式。然后编写底层驱动,以便于后续进行一些算法应用的部署
uboot
Kconfig 每个源码下提供选项
defconfig是系统的默认配置文件,系统编译这个文件就会将配置项保存带.config
.config 源码顶层目录下保存选择结果
makefile 每个源码目录下根据.config中的内容来告知编译系统应该编译什么
网络驱动
1、对比将nxp评估板原理图与之对比,发现ENET的复位引脚连接到了I.MX6ULL 的SNVS_TAMPER7
也就是说需要把SNVS_TAMPER7复用为gpio5_io07,在板级支持文件中找到io复位参数结构体,写上这个io的配置设置这个引脚无其他作用,而仅用于通用输入/输出(GPIO)功能
MX6_PAD_SNVS_TAMPER7__GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL)
2、原来的phy芯片是KSZ8081,现在是LAB8720a
1、看看芯片地址对不对
2、因为原来的驱动可以兼容现在的芯片,只需要找到phy驱动代码,找到通用的PHY驱动函数,将lan8720a相关的配置加入条件编译代码段,这样就可以识别我们的phy芯片
屏幕驱动
因为这个屏幕的lcd的io、背光io相同,只需要修改一些参数就可以,在板级支持包中找打显示结构体
像分辨率、屏幕型号、时钟周期
pixclock=(1/51200000)*10^12=19531
内核
网络驱动
内核驱动有兼容,只需要修改设备树和一些配置就可以
1、复位引脚
找到设备树删除SNVS_TAMPER7之前的配置,设置其为ENET的复位引脚gpio_io07找到“iomuxc_snvs节点,添加一个pinctrl节点添加io配置,设置为复位引脚
3、然后找到芯片驱动节点将上面pinctrl节点加入然后 加入gpio子系统中,并且修改芯片地址
4、最后使用图形化配置找到自带的phy驱动,使能就可以使用了
驱动代码直接使用nfs传输,设备树直接在开发板中修改设备树文件
1、修改好设备树,在内核顶层make dtbs ,然后替代tftp目录中的设备树文件
2、使用内核源码编译生成驱动程序,然后传送到开发板中,使用insmod动态加载
LCD驱动
(24 位色 RGB888,支持多点触摸)
一个像素点占据3个字节,rgb三个通道的值每个是0~255之间
1、初始化参数
这些参数都是屏幕自己独有的,设定好了的
2、初始化LCD像素时钟
3、设置RGBLCD显存
应用程序通过操作显存来操作LCD,在LCD上显示字符、图片等信息
显存需要申请,驱动程序设置的显存和应用程序访问的显存要是一物理地址
framebuff是显存抽象后的一种设备,允许上层应用程序在图形模式,framebuff是lcd硬件中的HAL(硬件抽象层)
通过将framebuff机制将LCD抽象为dev/fbx,应用程序通过操作/dev/fbx来操作lcd
lcd 控制器通用(半导体原厂),我们只需要修改屏幕参数
驱动程序
不需要写
NXPLinux 内核默认已经开启了 LCD 驱动,因此我们是可以看到/dev/fb0 这样一个设备
在mxfb.c中
请求·
设备树的修改
查看屏幕原理图io引脚是否与官方相同,不同就要修改设备树
lcdif 就是屏幕的设备树节点(删去了节点中的屏幕复位引脚,)
<&pinctrl_lcdif_dat //24 根数据线配置项。
&pinctrl_lcdif_ctrl>; //4 根控制线配置项,包括 CLK、ENABLE、VSYNC 和HSYNC
具体io配置
//修改数据线io的配置电气属性(ox79改成0x49降低了驱动能力)
pinctrl_lcdif_dat: lcdifdatgrp { //pinctrl_lcdif_dat标签,lcdifdatgrp是值
fsl,pins = <
MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x49 //MX6UL_PAD_LCD_DATA00__LCDIF_DATA00将被配置为具有0x49值的一个属性
MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x49
MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x49
MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x49
MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x49
MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x49
MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x49
MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x49
MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x49
MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x49
MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x49
MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x49
MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x49
MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x49
MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x49
MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x49
MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x49
MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x49
MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x49
MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x49
MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x49
MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x49
MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x49
>;
};
// 控制线io的配置
pinctrl_lcdif_ctrl: lcdifctrlgrp {
fsl,pins = <
MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x49//这个引脚是用于连接液晶显示屏接口(LCDIF)和处理器时钟总线(Clock Bus)的引脚。0x49表示该引脚的电气属性和功能设置
MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x49
MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x49
MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x49
>;
};
//背光io配置
pinctrl_pwm1: pwm1grp //LCD 背光 PWM 引脚配置项。
{
fsl,pins = <
MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0
>;
};
LCD 屏幕节点修改,修改相应的属性值,换成我们所使用的 LCD 屏幕参数
&lcdif {
pinctrl-names = "default";
// 指定LCD 所使用的 IO控制器 信息,这里使用了两个io控制器:pinctrl_lcdif_dat 和 pinctrl_lcdif_ctrl
pinctrl-0 = <&pinctrl_lcdif_dat
&pinctrl_lcdif_ctrl>;
//pinctrl_lcdif_dat为RGB LCD 的 24 根数据线配置项,24个io需要设置
//pinctrl_lcdif_ctrl4 根控制线配置项,4个io需要设置
display = <&display0>; // 指定使用的显示屏设备为 display0
status = "okay"; //设置设备节点的状态为 "okay",表示设备可以正常使用
display0: display 、//display0表示具体的某个设备名称,display表示该节点是一个显示器节点
{
bits-per-pixel = <32>; /*一个像素占用几个bit,使用rgb888*/
bus-width = <24>; /*总线宽度*/
display-timings {
native-mode = <&timing0>; /* 时序信息 */
timing0: timing0 {
clock-frequency = <51200000>; /* LCD 像素时钟,单位 Hz */
hactive = <1024>; /* LCD X 轴像素个数 */
vactive = <600>; /* LCD Y 轴像素个数 */
hfront-porch = <160>; /* LCD hfp 参数 */
hback-porch = <140>; /* LCD hbp 参数 */
hsync-len = <20>; /* LCD hspw 参数 */
vback-porch = <20>; /* LCD vbp 参数 */
vfront-porch = <12>; /* LCD vfp 参数 */
vsync-len = <3>; /* LCD vspw 参数 */
hsync-active = <0>; /* hsync 数据线极性 */
vsync-active = <0>; /* vsync 数据线极性 */
de-active = <1>; /* de 数据线极性 */
pixelclk-active = <1>; /* clk 数据线先极性 */
};
};
};
};
LCD 背光节点信息修改,要根据实际所使用的背光 IO 来修改相应的设备节点信息
pinctrl_pwm1: pwm1grp //LCD 背光 PWM 引脚配置项。
{
fsl,pins = <
MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0 //个引脚被用作PWM输出功能的引脚
>;
};
&pwm1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm1>;
status = "okay";
};
接下来我们依次来看一下上面这两个节点改如何去修改
最后在内核代码的第一个目录下运行命令,编译设备树
make dtbs
一些完善
设置 LCD 作为终端控制台
1、修改bootargs 参数
setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250:
/home/xzj/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:
of
console=tty1 设置屏幕作为控制台
console=ttymxc0,115200,也就是设置串口也作为控制台
开了两个控制台界面
2、修改/etc/inittab 文件(开机启动)
tty1::askfirst:-/bin/sh 设置LCD为终端
LCD 自动关闭解决方法
1、在drivers/tty/vt/vt.c找到blankinterval 设置为0
2、编写一个软件阻止息屏,并且设置为开机自动启动
开/etc/init.d/rcS
写入
cd /usr/bin
./lcd_always_on
cd ..
设备树与驱动程序匹配
lcd控制器节点
对应的驱动程序的compatible属性是fsl,imx6ull-lcdif
IIC驱动(传感器芯片是ap3216c)
linux内核基于驱动分离和分层的思想,将I2C驱动分成为
总线驱动:也就是i2c控制器驱动,也叫做适配器驱动(就是控制i2c设备的)
设备驱动:针对具体的i2设备而编写的驱动
两个重要的结构体(这些都在控制器驱动代码中)
i2c_adapter:i2c控制器结构体
在该结构体中有两个结构体
1、i2c——algorithm (控制器也i2c设备之间通信,使用下面的结构体进行具体的通信)
2、master_xfer i2c适配结构体(控制器)的传输函数
具体i2c设备驱动两个重要结构体
i2c_client和i2c_driver 前者描述设备信息,后者描述驱动内容
i2c_client;一个设备对应一个i2c_client,每检测到一个i2c设备就会给分配一个i2c_client
i2c_client:表示I2C设备,不需要我们自己创建i2c_client,我们一般在设备树里面添加具体的I2C芯片,比如fxls8471,系统在解析设备树的时候就会知道有这个I2C设备,然后会创建对应的i2c_client,
i2c_driver:类似platform驱动一样,有probe函数,匹配成功以后执行,同样还有设备树和非设备的匹配
i2c设备驱动框架,i2c_driver初始化与注册,需要II2C设备驱动编写人员编写的,IIC驱动程序就是初始化i2c_driver,然后向系统注册。注册使用i2c_register_driver、i2c_add_driver,如果注销i2c_driver使用i2c_del_driver
i2c_transform 会调用内核中的master_xfe用来进行数据传输
i2c_msgs 描述信息
msgs结构体包含的数据
地址
读写标志位
读取首地址
读取长度
添加I2C设备信息
在设备树中添加,I2C设备挂到那个I2C控制器下就在那个控制器下添加对应的节点
i2c设备的地址是七位
控制器的设备树代码在dtsi文件中,具体i2c设备的设备树文在imx.dts文件中
i2c设备数据收发流程
一般我们需要在probe函数中初始化i2c设备,就是必须能够对i2c设备寄存器进行读写操作
这里就是i2c_transfer函树,它会调用i2c_algorithm里面的master_xfer函数
设备树
通过原理图得知,ap3216c芯片接到了i2c1上,然后我们在设备树文件中找到i2c1控制器
这个节点,将我们ap3216c设备节点写在这个节点下
通常会使用一个或多个寄存器来控制一组引脚。这些寄存器可能包含位字段,每个位字段对应于特定的引脚功能、状态或配置选
该开发板设备树中好几个i2c控制器,i2c1 i2c2等
在dtsi设备树中找i2c控制器节点,然后通过compliate属性发现 控制器驱动在i2c-imx.c中
查看原理图发现,UART4_RXD这个io作为I2C1_SDA,UART4_TXD这个io作为I2C1_SCL
要设置某个引脚的作用需要设置对应的一个或者多个寄存器,我们不需要一个一个找,在与设备树同一目录下的imx6ull-pinfuns文件中找到
左边的这个宏定义就代表要设置UART4_TXD这个io作为I2C1_SCL的所有寄存器(也就是上面的几个)
芯片地址看数据手册
设备树代码
&i2c1 {
clock-frequency = <100000>;//12c频率
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;//控制的io口
status = "okay";
ap3216c@1e {
compatible = "alientek,ap3216c";//匹配属性
reg = <0x1e>;//i2c设备的地址
};
};
关于io地址
设备树中将具体的数据地址宏定义成文字
设备驱动程序
makefile文件
i2c_client 设备客户端也就是i2c设备,msg是传输的消息,在其中有属性表示是读还是写
读要两次操作
写只要一次操作
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "ap3216creg.h"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : ap3216c.c
作者 : 左忠凯
版本 : V1.0
描述 : AP3216C驱动程序
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/9/2 左忠凯创建
***************************************************************/
#define AP3216C_CNT 1
#define AP3216C_NAME "ap3216c"//设备节点的名字
//设备结构体
struct ap3216c_dev {
dev_t devid; /* 设备号 主设备号+次设备号 */
struct cdev cdev; /* cdev 字符设备对象,字符设备驱动的一种结构体类型 */
struct class *class; /* 类 用于创建设备节点,用于表示一组设备的类别。设备类通常包含相似或相关的设备,比如 USB 设备、网络设备 */
struct device *device; /* 设备 用于创建设备节点,则是一个具体的设备对象,它表示一个硬件设备在系统中的实例 */
struct device_node *nd; /* 设备节点,表示设备节点的一种结构体类型。设备节点是设备树中的一个节点。 */
int major; /* 主设备号 ,主设备号表示那种设备(字符、块)次设备号表示具体的设备*/
void *private_data; /* 私有数据 */
unsigned short ir, als, ps; /* 三个光传感器数据 */
};
static struct ap3216c_dev ap3216cdev;//
/*
* @description : 从ap3216c读取多个寄存器数据
* @param - dev: ap3216c设备
* @param - reg: 要读取的寄存器首地址
* @param - val: 读取到的数据
* @param - len: 要读取的数据长度
* @return : 操作结果
*/
//读取多个寄存器数据
/* 第一个消息用于发送要读取的寄存器地址,第二个则用于读取指定长度的数据 ,
通过使用 i2c_transfer() 函数来传输这两个消息,函数可以将读取到的数据保存到传入的 val 缓冲区中*/
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* ap3216c地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* ap3216c地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret = i2c_transfer(client->adapter, msg, 2);//第一个参数表示i2c适配器、第二个参数表示消息、第三个是发送消息的个数
if(ret == 2) //成功发送或者结束到两个消息就会返回消息数目
{
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%d
",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
/*
* @description : 向ap3216c多个寄存器写入数据
* @param - dev: ap3216c设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
//控制器向ap32168中写入多个数据
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];//定义一个长度为256的数组 b
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* ap3216c地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);//将msg数组进行传输
}
/*
* @description : 读取ap3216c指定寄存器值,读取一个寄存器
* @param - dev: ap3216c设备
* @param - reg: 要读取的寄存器
* @return : 读取到的寄存器值
*/
//通过i2c控制器向ap3216c芯片中读取一个寄存器的数据
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
u8 data = 0;
ap3216c_read_regs(dev, reg, &data, 1);
return data;
#if 0
struct i2c_client *client = (struct i2c_client *)dev->private_data;
return i2c_smbus_read_byte_data(client, reg);
#endif
}
/*
* @description : 向ap3216c指定寄存器写入指定的值,写一个寄存器
* @param - dev: ap3216c设备
* @param - reg: 要写的寄存器
* @param - data: 要写入的值
* @return : 无
*/
//通过i2c控制器向ap3216c指定寄存器写入指定的值,写一个寄存器
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ap3216c_write_regs(dev, reg, &buf, 1);
}
/*
* @description : 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
* : 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
* @param - ir : ir数据
* @param - ps : ps数据
* @param - ps : als数据
* @return : 无。
*/
//读取三个光传感器的数据,一个数据两个字节到dev中
void ap3216c_readdata(struct ap3216c_dev *dev)
{
unsigned char i =0;
unsigned char buf[6];
/* 循环读取所有传感器数据 */
for(i = 0; i < 6; i++)
{
buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i); //将寄存器中的数据赋值到数组中
}
if(buf[0] & 0X80) /* IR_OF位为1,则数据无效 */
dev->ir = 0;
else /* 读取IR传感器的数据 */
dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
dev->als = ((unsigned short)buf[3] << 8) | buf[2]; /* 读取ALS传感器的数据 */
if(buf[4] & 0x40) /* IR_OF位为1,则数据无效 */
dev->ps = 0;
else /* 读取PS传感器的数据 */
dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int ap3216c_open(struct inode *inode, struct file *filp)
{
filp->private_data = &ap3216cdev;
/* 初始化AP3216C */
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04); /* 复位AP3216C */
mdelay(50); /* AP3216C复位最少10ms */
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、PS+IR */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
short data[3];
long err = 0;
struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
ap3216c_readdata(dev);
data[0] = dev->ir;
data[1] = dev->als;
data[2] = dev->ps;
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int ap3216c_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* AP3216C操作函数 */
static const struct file_operations ap3216c_ops = {
.owner = THIS_MODULE,
.open = ap3216c_open,
.read = ap3216c_read,
.release = ap3216c_release,
};
/*
* @description : i2c驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : i2c设备
* @param - id : i2c设备ID
* @return : 0,成功;其他负值,失败
*/
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
/* 1、构建设备号 */
if (ap3216cdev.major) //如果主设备号存在
{
ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);//次设备号设备为0,合并成设备号
//注册设备号,第二个参数表示注册一个,第三个参数是设备名字(也就是设备节点名字)
register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
} else {
//注册设备号,次设备号从0开始
alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);
//分配主设备号
ap3216cdev.major = MAJOR(ap3216cdev.devid);
}
/* 2、注册设备 */
//
cdev_init(&ap3216cdev.cdev, &ap3216c_ops);//设备操作函数集 ap3216c_ops 和字符设备对象 ap3216cdev.cdev
//该函数调用的作用是将字符设备对象 ap3216cdev.cdev 添加到内核中,并将其与分配的设备号 ap3216cdev.devid 关联起来。
//这样操作系统就能够通过设备号来识别和操作这个字符设备了
cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
/* 3、创建类 */
//AP3216C_NAME 的设备类,并将其指针赋值给 ap3216cdev.class,以便后续的设备对象能够使用这个设备类
ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
//如果创建出错,会报错
if (IS_ERR(ap3216cdev.class)) {
return PTR_ERR(ap3216cdev.class);
}
/* 4、创建设备 */
//,用于在内核中创建一个新的设备对象,并将其注册到设备模型中。
//来创建一个名为 AP3216C_NAME 的设备对象,并将其指针赋值给 ap3216cdev.device。
//该设备对象属于 ap3216cdev.class 所指定的设备类,其设备号为 ap3216cdev.devid,
//此处的 NULL 参数表示设备对象的父设备为顶层总线设备,也就是没有父设备。创建成功后,该设备对象的指针被返回给 ap3216cdev.device
ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
if (IS_ERR(ap3216cdev.device)) {
return PTR_ERR(ap3216cdev.device);
}
ap3216cdev.private_data = client;
return 0;
}
/*
* @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
* @param - client : i2c设备
* @return : 0,成功;其他负值,失败
*/
static int ap3216c_remove(struct i2c_client *client)
{
/* 删除设备 */
cdev_del(&ap3216cdev.cdev);//从系统中删除一个字符设备
unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);// 是Linux内核中的一个函数,用于释放先前为字符设备保留的设备号区域
/* 注销掉类和设备 */
device_destroy(ap3216cdev.class, ap3216cdev.devid);//用于销毁之前创建的设备对象
class_destroy(ap3216cdev.class);///销毁名为 ap3216cdev 的设备类对象,这可能会在设备不再需要时调用,以便释放系统资源
return 0;
}
/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {
{"alientek,ap3216c", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "alientek,ap3216c" },
{ /* Sentinel */ }
};
/* i2c驱动结构体 */
static struct i2c_driver ap3216c_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.owner = THIS_MODULE,
.name = "ap3216c"
.of_match_table = ap3216c_of_match, //设备树匹配函数
},
.id_table = ap3216c_id,//无设备树匹配函数
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init ap3216c_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ap3216c_driver);//函数的返回值将被用于确定是否成功加载了驱动程序
return ret;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit ap3216c_exit(void)
{
i2c_del_driver(&ap3216c_driver);
}
/* module_i2c_driver(ap3216c_driver) */
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
触摸屏驱动
触摸芯片是FT5426
触摸点上报时序:type B
示例代码 64.1.3.1 Type B 触摸点数据上报时序
1 ABS_MT_SLOT 0 上报触摸点id
2 ABS_MT_TRACKING_ID 45 关联一个对触摸点进行添加、删除的东西
3 ABS_MT_POSITION_X x[0] 上报x坐标
4 ABS_MT_POSITION_Y y[0] 上报y坐标
5 ABS_MT_SLOT 1 上报第二个点
6 ABS_MT_TRACKING_ID 46
7 ABS_MT_POSITION_X x[1]
8 ABS_MT_POSITION_Y y[1]
9 SYN_REPORT 上报结束
驱动框架
1、12c驱动框架
2、复位引脚和中断引脚、包括中断
3、input子系统框架
4、初始化触摸ic、中断、input子系统
5、在中断服务函数里面读取触摸坐标值,然后上报坐标
最后就是在中断服务程序中上报读取到的坐标信息,根据所使用的多点电容触摸设备类型
选择使用 Type A 还是 Type B 时序。大多数的设备都是 Type B 类型