1、一个相对比较完整驱动模块,用到iic驱动触控ic芯片,当有触控事件发生时,int引脚会给主机一个中断信号用于告知有触控信号发生,input子系统将从ic中读到的信息上报给应用层。所以这里一共用到了三个驱动框架,但是归根到底,这个东西是一个iic外设,所以大框架是iic ,里面内嵌了input 中断这些。
2、打算用这个手码一下这个例程,巩固一下之前的知识,里面的用到的头文件,尽量都要从官方的文档获得根据,这样做好记录,之后要重新捡起来也方便。
1、设备树修改
2、设备驱动编写,iic总线架构linux内核已经实现了,这里只需要实现iic上挂载的设备就可以
这里面需要做的工作内容有:
- 头文件包含:
- 私有结构体的定义,用于驱动编写时,对一些官方结构体中不包含信息的扩展,里面就有感受,套娃的好处
- 基于任何一个iic从设备都需要的操作,编写of_match 表,初始化 iic 从设备驱动,将probe 和 remove成员函赋值。
- 完成模块注册,以及模块信息的填写,这里这里使用iic总线结构的方法,如果使用原始的方法,则需要调用module_init()和module_exit();
- 实例化probe函数和remove函数
- 本次项目中probe所承担的工作是1、注册触控ic这个设备(由于触控屏属于input架构下面的设备,所以注册的时候选用input架构,并且对input设备进行了参数设定,比如坐标点范围大小),并将这个设备添加到client 里面去 2、申请和一个中断、3、对触控ic进行复位和初始化配置
- 里面有关中断申请,ic复位初始化(需要用到iic数据传输),均在外部实现
- remove函数中主要是对设备资源的释放,由于前面的资源申请都采用demv_格式的函数,系统会自己释放,故只需要将注册的iic 从设备释放即可。
- 坐标信息的获取,通过ic的中断引脚发送脉冲,然后系统检测到中断信息,调用中断函数进行坐标信息的上抛。上抛方式采用type b ,详细信息可以看bingdings里面有关ts的介绍。
- iic读写数据,用到了tranfer 和msg传输机制,通过msg填充tranfer 之后使用数据同步函数,将数据发送出去,完全都是封装好的,读写操作类似。不同的是flags要设定,按照iic的协议来就可以。
- ic硬件复位函数,进行的工作和中断申请类似,显示从client 节点中找到reset-gpios这个属性,获取引脚编号,申请gpio,之后对gpio进行操作,这里用到了要给cansleep函数,还有没有找到这个的定义
- 源文件连接:
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
* Copyright (C) STMicroelectronics 2019 - All Rights Reserved
* Author: Alexandre Torgue <alexandre.torgue@st.com> for STMicroelectronics.
*/
/dts-v1/;
#include "stm32mp157.dtsi"
#include "stm32mp15xc.dtsi"
#include "stm32mp15-pinctrl.dtsi"
#include "stm32mp15xxaa-pinctrl.dtsi"
#include "stm32mp157-m4-srm.dtsi"
#include "stm32mp157-m4-srm-pinctrl.dtsi"
#include "stm32mp157d-atk.dtsi"
/ {
model = "STMicroelectronics STM32MP157C eval daughter";
compatible = "st,stm32mp157c-ed1", "st,stm32mp157";
chosen {
stdout-path = "serial0:115200n8";
};
aliases {
serial0 = &uart4;
};
reserved-memory {
gpu_reserved: gpu@f6000000 {
reg = <0xf6000000 0x8000000>;
no-map;
};
optee_memory: optee@fe000000 {
reg = <0xfe000000 0x02000000>;
no-map;
};
};
};
// 触摸屏用到的iic
&i2c3{
// iic总线补充
clock-frequency = <100000>; // 设置iic时钟频率
pinctrl-names = "default","sleep";
pinctrl-0 = <&pinctrl_i2c0_default>;
pinctrl-1 = <&pinctrl_i2c0_sleep>;
status = "okey";
//触控ic具体描述
touchscreen@38 {
compatible = "st,stm32mp157_ts";
reg = <0x38>; // 从机地址号
interrupt-parent = <&gpa1>; // 中断控制器来源
pinctrl-0 = <&touchscreen_reset_pins_a>; //复位引脚电气特征设置
interrupts = <1 IRQ_TYPE_NONE>; // 中断号,要选择中断来源
irq-gpios=<&gpioi 1 GPIO_ACTIVE_LOW>; // 中断引脚使用gpio 子系统,配置 i1 低电平有效
reset-gpios=<&gpioh 15 GPIO_ACTIVE_LOW>; // 复位引脚使用gpio 子系统,配置 和5 低电平有效
status = "okey";
};
};
// 触控ic 用到引脚电气特征定义
&pinctrl {
pinctrl_i2c0_default: ts_iic {
pins1 {
pinmux = <STM32_PINMUX('F', 12, scl)>, // 引脚复用
<STM32_PINMUX('F', 11, sda)>; // 这里要找对应的引脚,我就不找了
};
};
pinctrl_i2c0_sleep: ts_iic_sleep {
pins1 {
pinmux = <STM32_PINMUX('F', 12, anlog)>,
<STM32_PINMUX('F', 11, anlog)>;
};
};
touchscreen_reset_pins_a: ts_iic_reset_pins{
pins1 {
pinmux = <STM32_PINMUX('I', 1, GPIO)>,
<STM32_PINMUX('H', 15, GPIO)>;
bias-pull-up; //设置上拉
slew-rate=<0>; // 引脚速度等级为0
};
};
&cpu1{
cpu-supply = <&vddcore>;
};
&cryp1 {
status="okay";
};
&gpu {
contiguous-area = <&gpu_reserved>;
status = "okay";
};
&optee {
status = "okay";
};
ft5426.c
/* V1.0
ft5426 多点触摸屏驱动编写
由于这里面用到了多种子系统驱动框架,所以用来作为驱动编程的练习文档
1、 gpio 子系统
2、 platform 虚拟总线框架
3、 iic总线框架
4、 input子系统框架
5、 中断系统框架,还用到了中断转线程的操作,
减缓切入中断的资源消耗,不影响其它高优先级任务
*/
/* ----------- 头文件包含:说明来源及含义------------ */
#include <linux/module.h> // 模块注册相关
#include <linux/kernel.h>
#include <linux/irq.h>
#include <linux/interrupt/mt.h> // 中断处理相关及多点触碰
#include <linux/input.h> // 输入子系统相关
#include <linux/i2c.h> // iic相关
#include <linux/delay.h> // 有关sleep函数
#include <linux/of_gpio.h> // 有关gpio子系统?
/*-- -- -- -- --ft5426 相关寄存器定义-- -- -- -- -- -- -- -- --*/
#define FT5426_DEVIDE_MODE_REG 0x00 /* 模式寄存器 */
#define FT5426_TD_STATUS_REG 0x02 /* 状态寄存器 */
#define FT5426_TOUCH_DATA_REG 0x03 /* 触摸数据读取的起始寄存器 */
#define FT5426_ID_G_MODE_REG 0xA4 /* 中断模式寄存器 */
#define MAX_SUPPORT_POINTS 5 /* ft5426 最大支持 5 点触摸 */
#define TOUCH_EVENT_DOWN 0x00 /* 按下 */
#define TOUCH_EVENT_UP 0x01 /* 抬起 */
#define TOUCH_EVENT_ON 0x02 /* 接触 */
#define TOUCH_EVENT_RESERVED 0x03 /* 保留 */
/* ------------ ft5426 设备私有结构体定义 --------------------- */
struct edt_ft5426_dev
{
struct i2c_client *client; // iic 设备定义
struct input_dev *input; // input 设备定义
int reset_gpio; // 复位引脚编号
int irq_gpio; // 中断引脚编号
};
/* -------------- 实现 ft5426 写操作 -------------------------*/
static int ft5426_ts_write(struct edt_ft5426_dev *ft5426, u8 startaddr, u8 *buffer, u8 len)
{
struct i2c_client *client = ft5426->client;
struct i2c_msg msg[2]; // 一个用来发送地址,一个用来发送数据
int ret;
/* 正点原子将下面两个合并了,直接写成了一个msg*/
msg[0].flags = 0; // 写
msg[0].addr = client->addr; // 从设备地址
msg[0].buf = &startaddr; // 要写入寄存器
msg[0].len = 1; // 一个字节
msg[0].flags = 0; // 写
msg[0].addr = client->addr; // 从设备地址
msg[0].buf = buffer; // 发送寄存器
msg[0].len = len; // len个字节
/* 使用transfer 发送数据 */
ret = i2c_tranfer(client->adapter, msg, 2);
if (2 == ret)
return 0;
else
{
/* 发送失败 */
dev_err(&client->dev, "%s: write error,addr =0x%x len=%d .\n", __func__, addr, len);
}
}
/* --------------- 实现 ft5426 读操作------------------------ */
static int ft5426_ts_read(struct edt_ft5426_dev *ft5426, u8 startaddr, u8 *buffer, u8 len)
{
struct i2c_client *client = ft5426->client;
struct i2c_msg msg[2]; // 一个用来发送地址,一个用来接收数据
int ret;
msg[0].flags = 0; // 写
msg[0].addr = client->addr; // 从设备地址
msg[0].buf = &startaddr; // 要写入寄存器
msg[0].len = 1; // 一个字节
msg[0].flags = 1; // 读
msg[0].addr = client->addr; // 从设备地址
msg[0].buf = buffer; // 接收寄存器
msg[0].len = len; // len个字节
/* 使用transfer 发送数据 */
ret = i2c_tranfer(client->adapter, msg, 2);
if (2 == ret)
return 0;
else
{
/* 发送失败 */
dev_err(&client->dev, "%s: read error,addr =0x%x len=%d .\n", __func__, addr, len);
}
}
/* -------------- 触摸屏中断函数 ----------------------------- */
static irqreturn_t ft5426_ts_isr(int irq, void *dev_id)
{
struct edt_ft5426_dev *ft5426 = dev_id;
u8 rdbuf[30] = {0};
int i, type, x, y, id;
bool dowm;
int ret;
/* 先读取ft5426触摸点坐标,从0x02寄存器开始,连续读取29个寄存器 */
ret = ft5426_ts_read(ft5426, FT5426_TD_STATUS_REG, rdbuf, 29);
if (!ret)
{
goto out; // 读取失败
}
/* 读取坐标进行上抛,用的typeb模式 */
for (int i = 0; i < MAX_SUPPORT_POINTS; i++)
{
u8 *buf = &rdbuf[i * 6 + 1];
/* type b 需要获取当前坐标点的id 事件信息 */
type = buf[0] >> 6; // 获取 事件信息
if (type == TOUCH_EVENT_RESERVED)
continue; // 坐标点状态不过改变
/* 获取坐标点信息 */
x = (buf[1] << 8 | buf[0]) & 0x03ff;
x = (buf[2] << 8 | buf[3]) & 0x03ff;
id = buf[2] >> 4;
dowm = type != TOUCH_EVENT_UP; // 只要不是抬起都判定都按下
/* type b 坐标点上传,先传一个id */
input_mt_slot(ft5426->input, id);
input_mt_report_slot_state(ft5426->input, MT_TOOL_FINGER, down); // 上报slot 状态,按下,松开
if (!dowm)
continue; // 理解为当抬起手指的时候,之后坐标上报就不需要了,
input_report_abs(ft5426->input, ABS_MT_POSITION_X, x);
input_report_abs(ft5426->input, ABS_MT_POSITION_Y, y);
}
input_mt_report_pointer_emulation(ft5425->input, true); // 这个不知道啥意思
input_sync(ft5426->input); // 信息上抛同步
out:
return -1; // 表示中断出错
}
/* --------------- ft5426 硬件复位函数------------------------ */
static int ft5426_reset(struct edt_ft5426_dev *ft5426)
{
struct i2c_client *client = ft5426->client;
int ret;
/* 从设备树中获取复位引脚编号 */
ft5426->reset_gpio = of_get_name_gpio(client->dev.of_node, "reset-gpios", 0); // of_node 是一个节点链表,里面存放了子节点
if (!gpio_is_valid(ft5426->irq_gpio)) // 申请失败
{
dev_err(&client->dev, "failed to get ts reset gpio \n"); // 这个函数差不多,前面为什么加一个设备我有点不懂
return -1;
}
/* 申请使用管脚 */
ret = devm_gpio_request_one(&client->dev, ft5426->reset_gpio, GPIOF_IN, " ft5426 reset");
if (!ret)
return -1;
/* gpio 子系统操作 */
msleep(20);
gpio_set_value_cansleep(ft5426->reset_gpio, 0); // 拉低引脚
msleep(5);
gpio_set_value_cansleep(ft5426->reset_gpio, 1); // 拉高引脚
return 0;
}
/* ------------- 中断申请函数--------------------------------- */
/*
* 申请gpio的中断,绑定中断函数
*/
static int ft5426_ts_isq(edt_ft5426_dev *ft5426)
{
struct edt_ft5426_dev *ft5426 = i2c_get_clientdata(client); // 这里刚好用到了那个套娃的空参数
int ret;
/* 从设备树中获取中断引脚 */
ft5426->irq_gpio = of_get_name_gpio(client->dev.of_node, "irq-gpios", 0); // of_node 是一个节点链表,里面存放了子节点
if (!gpio_is_valid(ft5426->irq_gpio)) // 申请失败
{
dev_err(&client->dev, "failed to get ts interrupt gpio \n"); // 这个函数差不多,前面为什么加一个设备我有点不懂
return -1;
}
/* 申请使用管脚 */
ret = devm_gpio_request_one(&client->dev, ft5426->irq_gpio, GPIOF_IN, " ft5426 interrupt");
if (!ret)
return -1;
/* 注册中断服务函数,这个函数将中断转为线程,用于降低频繁进入中断导致的高任务调度不及时,但是不知道这个要包含什么头文件 */
ret = devm_request_threaded_irq(&client->dev, gpio_to_irq(ft5426->irq_gpio), NULL, ft5426_ts_isr,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, client->name, ft5426);
if (ret)
{
dev_err(&client->dev, "failed to request touchscreen irq \n"); // 这个函数差不多,前面为什么加一个设备我有点不懂
return -1;
}
return 0;
}
/* ------------ iic 设备 probe 和 remove --------------------- */
/* 里面需要完成的工作
* 1、注册iic 从设备
* 2、配置client 并注册
* 3、中断配置
* 4、配置ft5426,完成初始化
*/
static int ft5426_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct edt_ft5426_dev *ft5426;
struct input_dev *input;
u8 data;
int ret;
/* 实例化一个 edt_ft5426_dev 对象,采用动态申请*/
ft5426 = devm_kzalloc(&client->dev, sizeof(struct edt_ft5426_dev), GFP_KERNEL);
if (!ft5426) /* 申请空间不成功 */
{
dev_err(&client->dev, "Failed to allocate ft5426 device.\n");
return -1;
}
ft5426->client = client; // 将传递进来的client 赋值给自定义结构体
/* 复位FT5426 触摸芯片 */
ret = ft5426_reset(ft5426);
if (!ret) // 表示失败
return -1;
msleep(5);
/* 初始化 ft5426 */
data = 0;
ft5426_ts_write(ft5426, FT5426_DEVIDE_MODE_REG, &data, 1); // 配置基础寄存器
data = 1;
ft5426_ts_write(ft5426, FT5426_ID_G_MODE_REG, &data, 1); // 配置中断寄存器
/* 向内核申请 注册 中断服务函数,中断服务函数用于上报信息 */
ret = ft5426_ts_isq(ft5426);
if (!ret)
return -1; // 表示失败
//* 注册 input 设备 */
input = devm_input_allocate_device(&client->dev); // 这里怎么是device 设备哇,有点不太懂
if (!input)
{
dev_err(&client->dev, "failed to allocate input device");
return -1;
}
/* 配置私有结构体 */
ft5426->input = input;
input->name = "ft5425 touch screen";
input->id.bustype = BUS_I2C; // iic总线类型
/* 设置input输入信息 */
input_set_abs_params(input, ABS_MT_POSITION_X, 0, 1024, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y, 0, 600, 0, 0);
/* 初始化 slots */
ret = input_mt_init_slots(input, MAX_SUPPORT_POINTS, INPUT_MT_DIRECT);
if (ret)
{
dev_err(&client->dev, "fail init input_mt_init_slots");
return -1;
}
/* 注册 input 设备 */
ret = input_register_device(input);
if (ret)
return -1;
/* 怎么像在套娃,就是通过这个套娃,把这个私有变量传递到了 iic总线的client中,之后可以方便操作*/
i2c_set_clientdata(client, ft5426);
return 0;
}
/*
* 这里面主要完成的工作就是注销掉申请的 input 设备
*/
int ft5426_ts_remove(struct i2c_client *client)
{
struct edt_ft5426_dev *ft5426 = i2c_get_clientdata(client); // 这里刚好用到了那个套娃的空参数
input_unregister_device(ft5426->input); // 注销掉input设备
}
/* ----------- iic 总线 设备树匹配表--------------------------- */
static const strcut of_device_ft5426_compatible_match[] = {
{.compatible = "st,stm32mp157_ts"}, // 这个要和设备树中节点匹配值相同
{/* 必须保留一个空值 */}};
static struct i2c_driver ft5426_ts_driver = {
.driver = {
.owner = THIS_MOUDLE,
.name = "ft5426_ts", // 这个名字随便取
.of_match_table = of_match_ptr(of_device_ft5426_compatible_match), // 为iic-driver添加匹配表
},
.probe = ft5426_ts_probe, // 与设备树匹配后自动调用probe函数用于注册设备相关
.remove = ft5426_ts_remove, // 自动注销设备
};
module_i2c_driver(ft5426_ts_driver); // 用于注册iic总线框架,之后就可以调用上面的probe 和remove了
MODULE_LICENSE("GPL"); // 模块需要的协议
MODULE_AUTHOR("LAN"); // 作者名称
MODULE_INFO(intree, "Y"); // 模块信息