Linux驱动开发笔记

裸机驱动主要和寄存器打交道,有些MCU提供了相应的库,本质还是配置MCU的寄存器。运行Linux的MCU可以实现更加复杂的功能,使用Linux的驱动框架进行开发,将驱动工作进行分离和分层,提高驱动开发的效率。
Linux驱动分为三大类:字符设备驱动、块设备驱动、网络设备驱动

裸机驱动开发

汇编基础

Cortex-A芯片一上电SP指针还没初始化,必须先通过汇编语言设置C环境,例如初始化DDR,设置SP指针等工作,才能运行C代码。
IMX6UL芯片采用Cortex-A内核,因此需要使用Cortex-A的汇编指令,编译使用GCC交叉编译器,所以汇编代码需要符合GNU语法。

疑问
file_operation中每个操作函数的形参中inode的作用

file_operation定义了Linux内核驱动的所有的操作函数,每个操作函数与一个系统调用对应,对于字符设备来说,常用的函数有:llseekreadwritepool等等,这些操作函数都要需要完成实现。

file结构体代表一个打开的文件,其指针类型位filp。系统中每个打开的文件在内核空间中都关联一个file结构体,在打开文件时创建,在关闭后释放,其中定义了读写权限标志、读写模式标志、当前读写位置等信息,但其中在驱动中使用较多的是名为private_data的void类型的指针,大多被指向设备驱动中的设备结构体
inode结构体描述文件的访问权限、拥有者、所属组、文件大小、生成时间、访问时间、最后修改时间、设备号等信息,是Linux管理文件系统的最基本单位。其中在驱动中较为常用的信息是设备号,设备号被分为主设备号和次设备号,同一设备使用相同的主设备号,次设备号描述使用该驱动的设备序号

为实现“高内聚,低耦合”的软件设计理念,Linux驱动采用了内核加载的方式,将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用“insmod”命令加载驱动模块,用“rmmod”命令卸载具体驱动模块,相应地需要注册模块加载函数和模块卸载函数。

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模块的时候也需要注销掉字符设备。输入命令“cat /proc/devices”可以查看当前已经被使用掉的设备号

wangchenxiao@sdc-uvdise057:~$ cat /proc/devices
Character devices:
  1 mem
 ····
Block devices:
  2 fd
 ····

Linux 中每个设备都有一个设备号,设备号由主设备号次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备,设备号由dev_t数据类型描述,是一个32位的数据类型,中高 12 位为主设备号,其范围为 0~4095,所低 20 位为次设备号
Linux中每个设备号是独一无二的,申请新的设备号不能与已有的设备号重复,为避免冲突,使用alloc_chrdev_regiond unregister_chrdev_region动态申请和注销设备号

1.使用insmodmodprobe加载驱动模块。可以通过lsmod查看当前系统中存在的模块,模块加载后会注册设备,然后可以通过cat /proc/devices查看当前系统中存在的设备。
2.使用mknod创建设备对应的设备节点文件。通过ls /dev查看当前系统中的设备节点文件。设备节点文件是设备在用户空间中的实现,在应用程序中可以通过设备节点文件操作该设备。
3.不再使用该设备后,通过rmmod卸载设备。

cdev结构体描述了一个字符设备,其中定义了设备号dev_t和字符设驱动提供给虚拟文件系统的接口函数file_operations,驱动开发者需要编写其中接口函数。
用户空间调用Linux的API函数open打开设备时,设备驱动的open函数最终被调用。在驱动中大多在open函数中将file中的private_data指针指向设备结构体。

static int open(struct inode* inode, struct file* filp)

用户空间调用Linux的API函数close关闭设备时,release函数最终被调用。

static int close(struct inode* inode, struct file* filp)

用户空间调用Linux的API函数read读取数据时,read函数被调用

 

MMU(内存管理单元)完成虚拟内存到物理内存的映射和内存保护的功能。可以通过ioremap完成物理内存地址到虚拟内存地址的映射,相应在卸载内存时需要通过unremap释放虚拟内存的映射,在获得指向虚拟内存地址的指针后,可以通过readbreadwreadlwritebwritewwritel对内存进行读写操作

设备树

设备树通过树形数据结构描述板级设备信息,将板级信息从内核中分离出来,提升Linux驱动的灵活性。
通常SOC可以制作多个板子,SOC的信息对于这些板子来说是相同的,因此可以类似于C语言中的头文件,将这些共同的信息提取出来作为一个通用的dtsi文件,其他的dts文件引用该文件,并针对不同的设备进行相应的修改即可。
设备树compatible属性用于设备和驱动的绑定,指定该设备对应的驱动,而驱动模块中的of_device_id列表定义了该驱动对应的设备,设备首先按照先后顺序在内核里查找驱动模块,如果完成匹配则调用驱动的probe函数,完成初始化工作。在根节点中,compatible属性用于指定设备和SOC名称,Linux内核中由DT_MACHINE_START和MACHINE_END所定义的machine_desc 结构体中有个dt_compat 成员变量,其中保存了内核支持的设备,如果设备树中根节点下的compatible属性与dt_compat匹配,则表示Linux内核支持该设备,该开发板可以正常启动Linux内核。
编写驱动程序时可以通过Linux内核提供的API获取设备树上的设备信息,device_node描述了设备树上的一个节点,通常将device_node包含在设备结构体中,在初始化函数中使用of_find_node_by_path通过路径获取指定的节点,节点中的property保存了该节点的属性,可以通过of_find_property指定设备树节点和属性名称,获得相应的property结构体。
通过make dtbs命令将.dts编译为.dtb文件,启动内核后/proc/device-tree/目录下产生设备树上的所以节点
疑问:
1、根据Makefile了解设备树文件编译的具体规
2、根据Uboot移植和Linux内核移植了解dtb文件的具体使用过程。

pinctrl和gpio子系统

疑问:

用途:大多数的SOC的引脚是支持复用功能的,在裸机驱动开发中,设备的引脚和gpio通过设置相应的寄存器进行配置,在Linux驱动开发中引入了pinctrl和gpio子系统,通过在设备树中添加节点并完成引脚复用,电气等相应属性的配置,即可在驱动程序中通过提供的API函数完成引脚和gpio驱动的开发工作。
pinctrl子系统可以获取引脚信息,设置引脚的复用功能和电气特性

在设备树的外设节点下创建以pinctrl开头的节点,例如

pinctrl_led: ledgrp {
 fsl,pins = <
  MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
 >;
};

fsl,pins属性定义了引脚的复用属性和电气属性,宏定义由C语言的.h头文件定义,对应五个值,分别代表mux_reg的偏移地址、conf_reg的偏移地址、input_reg的偏移地址、mux_reg寄存器的值、input_reg寄存器的值,conf_reg寄存器的值设置电气属性,在该宏定义之外设置。疑问:如何通过寄存器的值设置引脚的复用和电气属性,具体都有哪些属性可以设置。
fsl,pins属性通过宏定义MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 将GPIO1_IO03复用为GPIO1_IO03,并设置电气属性为0X10B0。
1、同一个外设的 PIN 都放到一个节点里,因此需要在相应的外设节点中添加以pinctrl开头的节点,在其中添加fsl,pins属性,在其中设置引脚的复用和电气属性。
2、设置GPIO控制器节点,其中规定了GPIO控制器的寄存器节点,#gpio-cells节点规定了GPIO控制器cell的个数,第一个 cell 为 GPIO 编号,比如gpio1 3就表示 GPIO1_IO03。第二个 cell 表示GPIO 极 性, 如 果 为0(GPIO_ACTIVE_HIGH) 的 话 表 示 高 电 平有效 , 如 果 为1(GPIO_ACTIVE_LOW)的话表示低电平有效。gpio-controller将节点声明为GPIO控制器节点。
3、在设备节点中添加属性,pinctrl-n代表使用的pinctrl节点,说明该设备的复用和电气属性,在gpio属性中定义GPIO控制器节点,
4、在驱动程序中,首先在设备结构体中定义GPIO编号,在初始化函数里通过of_get_named_gpio读取设备节点的gpio属性,获得GPIO编号,然后通过gpio_direction_outputgpio_direction_input将GPIO编号对应的GPIO设置为默认输出或输入,然后在读取函数中通过gpio_get_value获取GPIO的值,在写入函数中通过gpio_set_value在相应的GPIO中写入值

再设备树的根节点下创建gpio对应的外设节点

gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
}

pinctrl-0属性设置了该节点对应的pintrcl节点,led-gpio属性设置了该设备对应的gpio节点信息,通过该属性可以获得GPIO编号

在驱动文件中
在设备结构体中定义GPIO编号
在初始化函数中根据of_find_node_by_path获得设备节点,通过of_get_named_gpio获得设备gpio编号,设置gpio输出输入属性
在写入函数中,通过gpio_set_value设置gpio的输出值。

Linux并发与竞争

原子变量:该变量的赋值和读取为原子操作
自旋锁:加锁后其他线程无法访问临界区,会处于忙等状态,因此适用于临界区耗时不长的场景。
信号量/互斥体:其他线程无法访问临界区时会进入休眠状态,进行上下文切换,适用于临界区较为耗时的场景。
同时只允许一个进程打开设备
通过在设备结构体中设置原子变量,初始化时设置该设备的原子变量为1,打开设备时原子变量减一,并判断是否小于零,若小于零则打开失败,关闭设备时原子变量加一。
在设备结构体中定义整形变量表示该设备是否使用,并定义自旋锁保护该变量,在初始化函数中初始化自旋锁,在打开设备时变量加一,如果变量大于零打开失败,关闭设备时变量减一,注意在对变量进行读写时需要通过自旋锁进行保护。
在设备结构体中定义信号量,在初始化函数中初始化信号量为1,在打开函数中获取信号量,若此时信号量为0则打开失败,在关闭函数中释放信号量。
在设备结构体中定义互斥体,在设备初始化函数中初始化互斥体,在打开设备函数中获取互斥体,如果获取互斥体失败则打开文件失败,在关闭函数中释放互斥体。

Linux内核定时器

Linux通过硬件定时器湖区时钟源,该时钟源的频率被称为系统频率或节拍率,该频率可以在编译LInux内核的时候设置。Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会将 jiffies 初始化为 0
在设备结构体中定义timer_list定时器结构体,在初始化函数中初始化定时器,添加超时时间和处理函数,并注册定时器,开始定时功能,在关闭函数中删除定时器。

Linux中断

裸机中断系统?
设备树中通过interrupt-parent指定相应的gpio中断控制器,通过interrupts设置引脚号和中断出发标志。gpio中断控制器节点中,通过interrupts设置该gpio中断源信息
在驱动文件的设备结构体中定义中断IO描述结构体,其中包含gpio、终端号、中断服务函数,名字等信息。通过of_get_named_gpio提取gpio号,然后根据irq_of_parse_and_map从设备树中获取中断号,完成中断处理函数后,通过request_irq完成中断的申请。在关闭函数中,释放中断号
在设备树中设置中断控制器,在节点中添加interrupt-controller属性将该节点声明为中断控制器,reg设置中断控制器的寄存器地址,#interrupt-cells设置中断的cell大小,

interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
 <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;

interrupts 描述中断源信息,如果是中断控制器节点,则#interrupt-cells设置为3,第一个代表中断类型,0 表示 SPI 中断,1 表示 PPI 中断,第二个代表:中断号,对于 SPI 中断来说中断号的范围为 0~ 987,对于 PPI 中断来说中断号的范围为 0~15,第三个代表标志位。如果是GPIO控制器节点,则#interrupt-cells设置为2,第一个代表GPIO的IO号,第二个代表标志位
GPIO控制器节点同时可以作为中断控制器节点。

在设备结构体中添加中断号和中断处理函数指针,然后在初始化函数中通过irq_of_parse_and_map获取中断号,然后将中断处理函数指针指向中断处理函数,然后通过request_irq以及中断号、中断处理函数,标志位等信息注册中断。

···
// 申请中断号
imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
···
// 根据中断号、中断处理函数等信息向内核注册
ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
				  imx6uirq.irqkeydesc[i].handler,
				  IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
				  imx6uirq.irqkeydesc[i].name, &imx6uirq);
···				  

中断处理函数原型

static irqreturn_t handler(int irq, void *dev_id)

阻塞和非阻塞IO

当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。对于非阻塞 IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃,设备驱动文件的默认读取方式就是阻塞式的,若要通过非阻塞方式打开,则需要再open函数中传入O_NONBLOCK

通过等待队列实现阻塞IO
在设备结构体中添加等待队列头结构体

平台设备驱动

在Linux内核中包含了大量驱动代码,因此必须提高驱动的可重用性。
驱动的分离:采用驱动、总线、设备信息模型,将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息,后根据获取到的设备信息来初始化设备,即于驱动只负责驱动,设备只负责设备,想办法将两者进行匹配即可。
在这里插入图片描述
驱动的分层:分层的目的也是为了在不同的层处理不同的内容。
定义platform_driver结构体,在其中设置驱动名称name,表示/sys/bus/platform/drivers/目录下的驱动名称,设备树匹配表of_match_table,用于和设备树中的compatible属性匹配,probe函数用于设备和驱动完成匹配时的执行,remove函数。
在初始化函数中通过platform_driver_register注册platform_driver结构体,在卸载函数中通过platform_driver_unregister卸载platform_driver结构体,
在probe函数中完成初始化,在remove函数中完成卸载。

 /* 设置platform_driver的设备匹配列表 */
static const struct of_device_id led_of_match[] = {
{ .compatible = "atkalpha-gpioled" },
{ /* Sentinel */ }
};
/* 定义platform_driver驱动结构体 */
static struct platform_driver led_driver = {
	.driver = {
	.name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */
	.of_match_table = led_of_match, /* 设备树匹配表 */
	},
	.probe = led_probe,
	.remove = led_remove,
 };

加载驱动后,/sys/bus/platform/drivers/目录下生成相应的驱动,由于在设备树中完成了设备的定义,/sys/bus/platform/devices/目录下会生成相应的设备,当驱动的platform_driverof_device_id列表中的compatible属性和设备树中的compatible属性匹配时,加载模块后会同时驱动和设备匹配成功,从而调用probe函数进行相应的初始化工作

misc驱动

misc为杂项驱动,主设备号为10。misc_register会完成申请设备号、初始化cdev、添加cdev、创建类、创建设备等操作,misc_registe会完成删除cdev、注销设备号、删除设备、删除类等操作

input子系统

Linux为输入设备创建的框架,统分为 input 驱动层、input 核心层、input 事件处理层。
在这里插入图片描述
首先在设备结构体定义一个input_dev指针,然后通过 input_allocate_device函数申请一个input_dev结构体,然后设置该结构体的名称,事件类型和事件值。input_devevbit表示输入事件的类型,需要通过以下方式设置输入事件的类型

/* 设置事件的事件类型 */
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |BIT_MASK(EV_REP);
/* 设置事件的事件码 */
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);

然后通过input_register_device向Linux内核注册结构体,卸载驱动模块时需要通过input_unregister_deviceinput_free_device注销和删除设备。
然后向input_eventLinux上报事件。在产生输入时,通过input_event根据事件类型和事件码上报对应的事件值。上报结束后通过input_sync通知内核上报结束。

加载驱动模块后,驱动会向Linux注册输入结构体,会在/dev/input目录下生成一个名eventX(X=0….n)的文件,这个/dev/input/eventX就是对应的 input 设备文件。
编写应用软件时,首先定义input_event结构体,其中type表示事件类型,code表示事件码,value表示按键值。在应用程序中通过open打开该input设备文件,然后通过read读取该设备文件中的输入事件,根据通过switch语句对不同的type事件类型进行处理

注:事件类型的定义如下

#define EV_SYN 0x00 /* 同步事件 */
#define EV_KEY 0x01 /* 按键事件 */
#define EV_REL 0x02 /* 相对坐标事件 */
#define EV_ABS 0x03 /* 绝对坐标事件 */
#define EV_MSC 0x04 /* 杂项(其他)事件 */
#define EV_SW 0x05 /* 开关事件 */
#define EV_LED 0x11 /* LED */
#define EV_SND 0x12 /* sound(声音) */
#define EV_REP 0x14 /* 重复事件 */
#define EV_FF 0x15 /* 压力事件 */
#define EV_PWR 0x16 /* 电源事件 */
#define EV_FF_STATUS 0x17 /* 压力状态事件 */

LCD
驱动文件基本无需更改,可以在设备树中修改相应的参数

I2C驱动

I2C驱动分为总线驱动和设备驱动。I2C总线驱动也被称为I2C控制器驱动或I2C适配器驱动,SOC的I2C总线驱动一般由半导体厂商编写,不需用户编写。I2C设备驱动针对不同的设备进行编写,是I2C驱动的重点。
platform_driver驱动框架类似,首先需要定义i2c_driver,并定义其中的proberemove函数,以及of_match_table设备树匹配列表,并完成设备树匹配列表中的compatible属性的定义,在驱动模块初始化函数中通过i2c_add_driver注册i2c_driver,在驱动模块卸载函数中通过i2c_del_driver注销i2c_driver,在probe函数中完成初始化工作,在remove函数中完成卸载工作。

static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)

在设备结构体中定义void*类型的private_data类型的指针,在probe函数中指向i2c_client结构体,之后即可通过设备结构体中的private_data指针获取i2c_client结构体,i2c_client结构体包含了芯片地址和adapter结构体。i2c_client 就是描述设备信息,每检测到一个I2C设备就会给这个I2C设备分配一个i2c_client。Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter,其中包含i2c_algorithm,定义了I2C 适配器与 IIC 设备进行通信的方法。i2c_msg定义了I2C通信的消息,其中定义了从机地址、消息数据、消息长度、标志位。通过i2c_transfer传递信息的时候需要传入i2c_client结构体的i2c_adapter

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 = &reg; /* 读取的首地址 */
	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);
	if(ret == 2) {
	ret = 0;
	} else {
	printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
	ret = -EREMOTEIO;
	}
	return ret;
}

static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len){

	u8 b[256];
	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);
}

I2C读数据需要定义两个i2c_msg,第一个i2c_msg描述了读取寄存器的首地址,第二个i2c_msg描述了读取寄存器的数据,I2C写数据需要定义一个256长度的u8数组,首先保存寄存器的首地址,然后保存需要发送的数据,最后调用i2c_transfer完成数据的读写操作。

SPI驱动

SPI驱动和I2C驱动类似,分为主机驱动和设备驱动,其中主机驱动一般由SOC厂商编写,因此通常需要实现设备驱动。
首先需要定义SPI驱动结构体spi_driver,定义设备树匹配列表,在其中定义compatible属性,定义probe函数,在其中设置spi_device的mode属性,并通过spi_setup完成设备的初始化工作,定义remove函数,完成设备的卸载工作,在初始化函数中通过spi_register_driver注册spi_driver,在卸载函数中通过spi_unregister_driver注销spi_driver。
spi_transfer定义了SPI传输中的数据,其中tx_buf定义了发送数据,rx_buf定义了接收数据,len定义了长度,然后需要定义spi_message,并通过spi_message_init完成初始化,通过spi_message_add_tail将spi_transfer添加至spi_message中,最后通过spi_sync发送
对于SPI读取数据来说,首先要片选拉低,发送需要读取的寄存器地址,然后读取数据,最后拉高片选;对于SPI写数据来说,首先片选拉低,发送需要读取的寄存器地址,然后发送数据,最后拉高片选。

UART驱动框架

串口驱动没有什么主机端和设备端之分,就只有一个串口驱动,通常由SOC厂商完成编写,只需要在设备树中添加串口节点信息,系统启动以后串口驱动和设备匹配成功,相应的串口就会被驱动。

电容触摸屏驱动

电容屏驱动框架
1、电容触摸屏是通过I2C接口与主机通信的,因此需要I2C驱动作为整体框架
2、触摸IC提供中断信号引脚来获得中断触摸信息,因此需要申请中断号,并注册中断处理函数
3、触摸屏幕会产生坐标、按下抬起信息,需要通过input子系统向Linux内核上报信息
4、需要通过MT协议上报触摸屏幕信息,一般来说,对于支持多点电容触摸的屏幕使用Type B类型的MT协议

首先在probe函数中,通过devm_input_allocate_device申请input_dev,并进行初始化,注册需要上报的事件evbitEV_KEYEV_ABSEV_KEY上报触摸屏是否被按下,EV_ABS上报触摸点的坐标,设定上报的按键码keybitBTN_TOUCH

/* 3,input 设备申请与初始化 */
	input = devm_input_allocate_device(&client->dev);

	// TODO 一下代码的含义
	input->name = client->name;
	input->id.bustype = BUS_I2C;
	input->dev.parent = &client->dev;

	__set_bit(EV_KEY, ft5x06.input->evbit);
	__set_bit(EV_ABS, ft5x06.input->evbit);
	__set_bit(BTN_TOUCH, ft5x06.input->keybit);

调用 input_set_abs_params 函数设置 EV_ABS 事件需要上报 ABS_XABS_YABS_MT_POSITION_XABS_MT_POSITION_Y。单点触摸需要上报 ABS_XABS_Y,对于多点触摸需要上报 ABS_MT_POSITION_XABS_MT_POSITION_Yinput_set_abs_paramsh函数原型如下:

void input_set_abs_params(struct input_dev *dev, unsigned int axis, int min, int max, int fuzz, int flat)
形参: dev --input_dev结构体
   axis --上报的数值
   min --最小值
   max --最大值
    fuzz --数据偏差值
    flat --平滑位置
设置触摸屏x坐标范围:
 input_set_abs_params(touch_dev,ABS_X,0,800,0,0);//设置x坐标范围
设置触摸屏x坐标范围:
 input_set_abs_params(touch_dev,ABS_PRESSURE,0,1,0,0);//设置压力值范围

input_mt_init_slotsinput_dev进行SLOT的初始化,并指定触摸点的数量。

input_set_abs_params(input, ABS_X, 0, width, 0, 0);
input_set_abs_params(input, ABS_Y, 0, height, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_X,0, width, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y,0, height, 0, 0); 
input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0);

申请中断号,并在Linux内核中注册中断处理函数。触摸屏的中断需要进行中断线程化。硬件中断发生的时候内核会暂停当前执行的操作,转而执行中断处理程序,由于触摸中断的产生十分频繁,从而会使得内核频繁地暂停当前的操作指向中断程序,使得任务无法得到及时的处理,而中断线程化可以将中断作为具有一定优先级的内核线程运行,从而使得优先级更高的任务得到及时处理。

ret = devm_request_threaded_irq(&client->dev, 
								client->irq, 
								NULL, 
								ft5x06_handler, 
								IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 
								client->name,&ft5x06);

在中断处理函数中通过I2C读取寄存器数据,获得每个触摸点的信息,首先通过input_mt_slot上报触摸点的ID,然后通过input_mt_report_slot_state上报触摸类型,一般是MT_TOOL_FINGER,然后通过input_report_abs上报xy坐标信息。所有触摸点的信息上班完成后,通过input_mt_report_pointer_emulation上报追踪到的触摸点数量是否多余当前上报的触摸点数量,最后由input_sync宣布事件上报结束


...
	// 用于产生 ABS_MT_SLOT 事件,告诉内核当前上报的是哪个触摸点的坐标数据
	input_mt_slot(multidata->input, id);
	// 用于产生 ABS_MT_TRACKING_ID 和 ABS_MT_TOOL_TYPE事件, ABS_MT_TRACKING_ID 事 件 给 slot 关联一个 ABS_MT_TRACKING_ID ,ABS_MT_TOOL_TYPE 事 件 指 定 触 摸 类 型 ( 是 笔 还 是 手 指 等 )。
	input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down);
	if (!down)
		continue;
	// 此函数上报触摸点坐标信息,通过 ABS_MT_POSITION_X 和ABS_MT_POSITION_Y 事 件 实 现 X 和 Y 轴 坐 标 信 息 上 报
	input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
	input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
}
// 报追踪到的触摸点数量是否多余当前上报的触摸点数量
input_mt_report_pointer_emulation(multidata->input, true);
// 宣布事件上报结束
input_sync(multidata->input);

疑问:input_dev结构体中以下成员函数的作用,以及probe函数中以下代码的作用

ft5x06.input->name = client->name;
ft5x06.input->id.bustype = BUS_I2C;
ft5x06.input->dev.parent = &client->dev;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值