linux驱动开发笔记记录-多点触摸屏驱动

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函数,还有没有找到这个的定义
  • 源文件连接:

stm32mp157d-lan.dts

// 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"); // 模块信息

  • 22
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值