前言:
学习这个input子系统是为了看当下项目中ili25xxtp的遇到的问题,也是对自己的一种积累。当前项目用到tp : ili25xx 触控芯片,在linux debian10上,由于系统对触控类设备支持不太友好,上层一些app bt network出现触控不准,触控不到问题。对比鼠标,app能正常工作。通过对比:
鼠标上报的是相对坐标rel,上层点击正常tp上报的是绝对坐标abs,上层触控异常
所以想通过改tp上报的键值,让tp像鼠标一样上报相对坐标,以下是坐标的改动部分,下周验证以下;同时还要分别看一下鼠标点击和触控点击的上报事件,估计也要改一下.还要验证一种情况,tp上报的键值通过虚拟鼠标上报到上层
//<1>坐标改动部分:static int ili210x_i2c_probe(struct i2c_client *client,const struct i2c_device_id *id){..../* Setup input device */input->name = "ILI210x Touchscreen";input->id.bustype = BUS_I2C;input->dev.parent = dev;__set_bit(EV_SYN, input->evbit);__set_bit(EV_KEY, input->evbit);__set_bit(EV_ABS, input->evbit);+ __set_bit(EV_REL, input->evbit);__set_bit(BTN_TOUCH, input->keybit);.....}+struct xy_rel{+ unsigned int x;+ unsigned int y;+};++static xy_rel il20_rel;static void ili210x_report_events(struct input_dev *input,const struct touchdata *touchdata){int i;bool touch;unsigned int x, y;const struct finger *finger;for (i = 0; i < MAX_TOUCHES; i++) {input_mt_slot(input, i);finger = &touchdata->finger[i];touch = touchdata->status & (1 << i);input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);if (touch) {x = finger->x_low | (finger->x_high << 8);y = finger->y_low | (finger->y_high << 8);-//input_report_abs(input, ABS_MT_POSITION_X, x);-//input_report_abs(input, ABS_MT_POSITION_Y, y);+input_report_rel(input, REL_X,il20_rel.x-x);+input_report_rel(input, REL_Y,il20_rel.y-y); + il20_rel.x=x;+ il20_rel.y=y;}}input_mt_report_pointer_emulation(input, false);input_sync(input);}//<2>触摸点击改为鼠标点击:暂无
doit:
input输入的意思,因此input子系统管理输入的子系统,和pinctl和gpio子系统一样,针对某一类设备而创建的框架。比如:按键输入,鼠标、键盘、触摸屏等设备。
不同的设备代表的信息不同,因此在应用层的处理就不同。在驱动开发中,不需要关心应用层的事情,只需要按照要求上报这些输入时间。因此input 子系统,分为input 驱动层、核心层、事件处理层,最终给用户空间提供的是可访问的设备节点,框架如下:
1. linux驱动编写流程
input核心层回想linux内核注册一个字符设备,在drivers/input/input.c 文件中。
代码:
struct class input_class{.name = "input",.devnode = input_devnode,}static int __init input_init(void){...err = class_register(&input_class);...err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),INPUT_MAX_CHAR_DEVICES, "input");....}
class_register 就会注册一个input类,这样在系统启动以后,在/sys/class目录哟有一个input子目录。
register_chrdev_region,注册一个字符设备,主设备号INPUT_MAJOR,为13( include/uapi/linux/major.h),所以input子系统中主设备号都为13,。在使用input子系统处理输入设备就不需要注册字符设备,只需要向系统注册一个input_device。
1. 注册input_dev
秩序注册一个input设备,input_dev结果提表示input设备,struct定义在include/linux/input.h。
如下:
struct input_dev {const char *name;const char *phys;const char *uniq;struct input_id id;unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的*/unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的*/unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标的*/ unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 绝对坐标的*/unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 杂项事件的*/unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /*LED 相关的*/unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];/* sound 有关的*/unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 压力反馈的*/unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /*开关状态的*/......bool devres_managed;}
其中,evbit表示表示输入事件类型,定义在
include/uapi/linux/input.h ,时间类型如下:
#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 /* 压力状态事件 */
如果视同按键,就需要注册EV_KEY 事件,如果使用连按,还需要注册EV_REP事件。
linux 内核定义了很多按键值,定义在include/uapi/linux/input.h。如下:
#define KEY_RESERVED 0#define KEY_ESC 1#define KEY_1 2#define KEY_2 3#define KEY_3 4#define KEY_4 5#define KEY_5 6#define KEY_6 7#define KEY_7 8#define KEY_8 9#define KEY_9 10#define KEY_0 11....
在编写驱动的时候,申请一个input_dev结构体变量,使用input_allocate_device 函数来申请一个 input_dev,函数原型:
truct input_dev *input_allocate_device(void)
注销 ,释放inpu_dev ,函数void input_free_device(struct input_dev *dev)
申请一个input_dev,后初始化input_dev,
然后向linux 内核注册设input_dev
int input_register_device(struct input_dev *dev)
注销input_dev设备时,用 input_unregister_device函数注册,函数原型
void input_unregister_device(struct input_dev *dev)
注册过程如下:1->使用input_allocate_device函数申请一个input_dev.2-> 初始化input_dev的事件类型和事件值3->使用input_unregister_device 函4-> 卸载input 驱动,先用input_unregister_device注消注册好的input_dev,然后用input_free_device函数释放掉前面申请的input_dev。框架如下:struct input_dev *inputdev; /* input变量 *///驱动入口函数static init __init xxx_init(void){...inputdev = input_allocate_device(); //申请input_devinputdev->name = “test” //设置input_dev name....设置事件和事件值__set_bit(EV_KEY, inputdev->evbit); //设置产生按键事件 __set_bit(EV_REP, inputdev->evbit); // 重复事件 //__set_bit(KEY_0, inputdev->keybit); //设置产生哪些按键值 input_register_device(inputdev); //注册inp_dev...return 0;}//驱动出口函数static void __exit xxx_exit(coid){input_unregister_device(inputdev);// 注销inpu——devinput_free_device(inputdev);//释放,删除input dev}
2.上报输入事件
input设备都是具有输入功能的,具体是什么样的设备,linux 内核是不知道的,需要获得剧透的输入值,事件,上报给linux 内核,比如按键,需要在按键的中断函数中,或者定时间中断中将案件上报个哦linux内核,这样linux 内核才能获得正确的输入值。
不同的事件,用不用的api,一下是常用的上报api。
input_event :原型:void input_event(struct input_dev *dev, .//上报设备unsigned int type, //类型EV_KEYunsigned int code, //事件吗 KEY_0int value)//事件值 1,0按键上报api:static inline void input_report_key(struct input_dev *dev,unsigned int code, int value) {input_event(dev, EV_KEY, code, !!value);//调用input event}其它上报函数void input_report_rel(struct input_dev *dev, unsigned int code, int value)//后面用到void input_report_abs(struct input_dev *dev, unsigned int code, int value)//后面用到我们上报事件之后,还要用input sync函数告诉linux 内核,input子系统上报结束,input_sync函数本质是上报一个同步事件,函数为:void input_sync(struct input_dev *dev)
3.按键上报实例:
voide timefun(){u8 value;value = gpio_get_vale(xx);if(value)input_report_key(inputdev, xxx, 1);input_sync(inputdev);elseinput_report_key(inputdev, xxx, 0);input_sync(inputdev);}
input_event struct
struct input_event {struct timeval time;__u16 type;__u16 code;__s32 value;};struct timeval time;struct timeval { __kernel_time_t tv_sec; //s__kernel_suseconds_t tv_usec; //us};
至此我们就可以去看代码,编写一个按键上报练:
下面贴一个内核ili210x.c驱动程序,框架是厂家基于以上1,2,3编写的。
#include <linux/module.h>#include <linux/i2c.h>#include <linux/interrupt.h>#include <linux/slab.h>#include <linux/input.h>#include <linux/input/mt.h>#include <linux/delay.h>#include <linux/workqueue.h>#include <linux/input/ili210x.h>#define MAX_TOUCHES2#define DEFAULT_POLL_PERIOD20/* Touchscreen commands */#define REG_TOUCHDATA0x10#define REG_PANEL_INFO0x20#define REG_FIRMWARE_VERSION0x40#define REG_CALIBRATE0xccstruct finger {u8 x_low;u8 x_high;u8 y_low;u8 y_high;} __packed;struct touchdata {u8 status;struct finger finger[MAX_TOUCHES];} __packed;struct panel_info {struct finger finger_max;u8 xchannel_num;u8 ychannel_num;} __packed;struct firmware_version {u8 id;u8 major;u8 minor;} __packed;struct ili210x {struct i2c_client *client;struct input_dev *input;bool (*get_pendown_state)(void);unsigned int poll_period;struct delayed_work dwork;};static int ili210x_read_reg(struct i2c_client *client, u8 reg, void *buf,size_t len){struct i2c_msg msg[2] = {{.addr= client->addr,.flags= 0,.len= 1,.buf= ®,},{.addr= client->addr,.flags= I2C_M_RD,.len= len,.buf= buf,}};if (i2c_transfer(client->adapter, msg, 2) != 2) {dev_err(&client->dev, "i2c transfer failed\n");return -EIO;}return 0;}struct xy_rel{unsigned int x;unsigned int y;};static xy_rel il20_rel;static void ili210x_report_events(struct input_dev *input,const struct touchdata *touchdata){int i;bool touch;unsigned int x, y;const struct finger *finger;for (i = 0; i < MAX_TOUCHES; i++) {input_mt_slot(input, i);finger = &touchdata->finger[i];touch = touchdata->status & (1 << i);input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);if (touch) {x = finger->x_low | (finger->x_high << 8);y = finger->y_low | (finger->y_high << 8);//input_report_abs(input, ABS_MT_POSITION_X, x);//input_report_abs(input, ABS_MT_POSITION_Y, y);input_report_rel(input, REL_X,il20_rel.x-x);input_report_rel(input, REL_Y,il20_rel.y-y); il20_rel.x=x;il20_rel.y=y;}}input_mt_report_pointer_emulation(input, false);input_sync(input);}static bool get_pendown_state(const struct ili210x *priv){bool state = false;if (priv->get_pendown_state)state = priv->get_pendown_state();return state;}static void ili210x_work(struct work_struct *work){struct ili210x *priv = container_of(work, struct ili210x,dwork.work);struct i2c_client *client = priv->client;struct touchdata touchdata;int error;error = ili210x_read_reg(client, REG_TOUCHDATA,&touchdata, sizeof(touchdata));if (error) {dev_err(&client->dev,"Unable to get touchdata, err = %d\n", error);return;}ili210x_report_events(priv->input, &touchdata);if ((touchdata.status & 0xf3) || get_pendown_state(priv))schedule_delayed_work(&priv->dwork,msecs_to_jiffies(priv->poll_period));}static irqreturn_t ili210x_irq(int irq, void *irq_data){struct ili210x *priv = irq_data;schedule_delayed_work(&priv->dwork, 0);return IRQ_HANDLED;}static ssize_t ili210x_calibrate(struct device *dev,struct device_attribute *attr,const char *buf, size_t count){struct i2c_client *client = to_i2c_client(dev);struct ili210x *priv = i2c_get_clientdata(client);unsigned long calibrate;int rc;u8 cmd = REG_CALIBRATE;if (kstrtoul(buf, 10, &calibrate))return -EINVAL;if (calibrate > 1)return -EINVAL;if (calibrate) {rc = i2c_master_send(priv->client, &cmd, sizeof(cmd));if (rc != sizeof(cmd))return -EIO;}return count;}static DEVICE_ATTR(calibrate, S_IWUSR, NULL, ili210x_calibrate);static struct attribute *ili210x_attributes[] = {&dev_attr_calibrate.attr,NULL,};static const struct attribute_group ili210x_attr_group = {.attrs = ili210x_attributes,};static int ili210x_i2c_probe(struct i2c_client *client,const struct i2c_device_id *id){struct device *dev = &client->dev;const struct ili210x_platform_data *pdata = dev_get_platdata(dev);struct ili210x *priv;struct input_dev *input;struct panel_info panel;struct firmware_version firmware;int xmax, ymax;int error;dev_dbg(dev, "Probing for ILI210X I2C Touschreen driver");if (!pdata) {dev_err(dev, "No platform data!\n");return -EINVAL;}if (client->irq <= 0) {dev_err(dev, "No IRQ!\n");return -EINVAL;}/* Get firmware version */error = ili210x_read_reg(client, REG_FIRMWARE_VERSION,&firmware, sizeof(firmware));if (error) {dev_err(dev, "Failed to get firmware version, err: %d\n",error);return error;}/* get panel info */error = ili210x_read_reg(client, REG_PANEL_INFO, &panel, sizeof(panel));if (error) {dev_err(dev, "Failed to get panel information, err: %d\n",error);return error;}xmax = panel.finger_max.x_low | (panel.finger_max.x_high << 8);ymax = panel.finger_max.y_low | (panel.finger_max.y_high << 8);priv = kzalloc(sizeof(*priv), GFP_KERNEL);input = input_allocate_device();if (!priv || !input) {error = -ENOMEM;goto err_free_mem;}priv->client = client;priv->input = input;priv->get_pendown_state = pdata->get_pendown_state;priv->poll_period = pdata->poll_period ? : DEFAULT_POLL_PERIOD;INIT_DELAYED_WORK(&priv->dwork, ili210x_work);/* Setup input device */input->name = "ILI210x Touchscreen";input->id.bustype = BUS_I2C;input->dev.parent = dev;__set_bit(EV_SYN, input->evbit);__set_bit(EV_KEY, input->evbit);__set_bit(EV_ABS, input->evbit);__set_bit(EV_REL, input->evbit);__set_bit(BTN_TOUCH, input->keybit);/* Single touch */input_set_abs_params(input, ABS_X, 0, xmax, 0, 0);input_set_abs_params(input, ABS_Y, 0, ymax, 0, 0);/* Multi touch */input_mt_init_slots(input, MAX_TOUCHES, 0);input_set_abs_params(input, ABS_MT_POSITION_X, 0, xmax, 0, 0);input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ymax, 0, 0);i2c_set_clientdata(client, priv);error = request_irq(client->irq, ili210x_irq, pdata->irq_flags,client->name, priv);if (error) {dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n",error);goto err_free_mem;}error = sysfs_create_group(&dev->kobj, &ili210x_attr_group);if (error) {dev_err(dev, "Unable to create sysfs attributes, err: %d\n",error);goto err_free_irq;}error = input_register_device(priv->input);if (error) {dev_err(dev, "Cannot register input device, err: %d\n", error);goto err_remove_sysfs;}device_init_wakeup(dev, 1);dev_dbg(dev,"ILI210x initialized (IRQ: %d), firmware version %d.%d.%d",client->irq, firmware.id, firmware.major, firmware.minor);return 0;err_remove_sysfs:sysfs_remove_group(&dev->kobj, &ili210x_attr_group);err_free_irq:free_irq(client->irq, priv);err_free_mem:input_free_device(input);kfree(priv);return error;}static int ili210x_i2c_remove(struct i2c_client *client){struct ili210x *priv = i2c_get_clientdata(client);sysfs_remove_group(&client->dev.kobj, &ili210x_attr_group);free_irq(priv->client->irq, priv);cancel_delayed_work_sync(&priv->dwork);input_unregister_device(priv->input);kfree(priv);return 0;}static int __maybe_unused ili210x_i2c_suspend(struct device *dev){struct i2c_client *client = to_i2c_client(dev);if (device_may_wakeup(&client->dev))enable_irq_wake(client->irq);return 0;}static int __maybe_unused ili210x_i2c_resume(struct device *dev){struct i2c_client *client = to_i2c_client(dev);if (device_may_wakeup(&client->dev))disable_irq_wake(client->irq);return 0;}static SIMPLE_DEV_PM_OPS(ili210x_i2c_pm,ili210x_i2c_suspend, ili210x_i2c_resume);static const struct i2c_device_id ili210x_i2c_id[] = {{"ili210x", 0 },{}};MODULE_DEVICE_TABLE(i2c, ili210x_i2c_id);static struct i2c_driver ili210x_ts_driver = {.driver = {.name = "ili210x_i2c",.pm = &ili210x_i2c_pm,},.id_table = ili210x_i2c_id,.probe = ili210x_i2c_probe,.remove = ili210x_i2c_remove,};module_i2c_driver(ili210x_ts_driver);MODULE_AUTHOR("Olivier Sobrie <olivier@sobrie.be>");MODULE_DESCRIPTION("ILI210X I2C Touchscreen Driver");MODULE_LICENSE("GPL");
如果觉得《input 子系统(调试 ili251x-tp)》对你有帮助,请点赞、收藏,并留下你的观点哦!