Amlogic红外识别驱动分析

0 阅读43分钟

前言:

写这个的初心主要是当时面试一些芯片厂家的时候,他们指出了我当前存在深度不足的一个问题。这里的话我就按照我之前接触的比较多的amlogic进行分析和深入。这里主要是会对amlogic的一个红外识别驱动进行分析和学习

红外介绍

红外是我们在生活里面用的比较多的一种远程控制的一个方法,他存在的众多的一个协议,比如NEC,SONY,NRC等,对于本次的一个驱动介绍,我将会从我们用的最多的一个NEC协议来进行讲解,这里会涉及到一些红外的时序以及一些对应的红外格式等等,我们学习驱动对于底层的原理至少要做到了解的程度,如此才能更好的去做驱动。

NEC协议介绍

对于一个NEC协议的红外信号,他的标准格式如下,他由引导码,地址码,地址码(反码),数据码,数据码(反码)构成,其中地址码和数据码的反码主要是用于做数据校验,在获取到数据的时候,通过比对这两个码的一个关系,来查看是否出现了差错 image.png

NEC 发送时序介绍

对于NEC,他的引导码和数据0和1的一个表示有其特别的一个时序,这个主要是通过上拉或者下拉的一个时长有关系。下面我们一一讲解

NEC 引导码

引导码可以让红外接收头自动增益控制(AGC)调整到合适电平,防止环境光干扰。同时,引导码也作为帧起始标志,使接收端能同步时序。而对于我们红外发送中,我们存在一个短按和长按的这么一个区别,其中对于长按,为了能够减少功耗和总线占用,当按键重复的时候,我们不会重复一直发送完整的数据帧,而是发送一个重复码的引导码,由此,接收端只需要查看引导码即可判断当前按键的状态,大大减低了一个资源的浪费

image.png 对于引导码,其由一个9ms的低电平和一个4.5ms的高电平组成,重复码的话,则是在我们正常引导码的基础上缩短了高电平的时间,变更为2.25ms。

NEC 数据高低电平的一个表现形式

对于NEC的数据传输,由于他是一种无时钟信号的一个异步通讯,所以其为了表示数据的高电平和低电平主要通过不同的一个时间以及一个特殊的波形来完成

image.png

从上面的波形可以看到,数据0和数据1他们最核心的一个区别就是数据帧的一个总体时长的不同,对于数据1,他的总体时长是数据0的时间的2倍。NEC数据传输主要通过上面的形式来表示不同的数据

amlogic驱动实现介绍

在了解了红外NEC协议之后,我们来看看amlogic红外的驱动实现,在这一部分里面,我们重点关注他的NEC部分,对于amlogic的红外驱动,从设备树里面我们查阅到他对应的驱动是

基于这个compatible,我们找到了他的对应文件,remote_meson.c

在进行代码讲解之前,我先说一下amlogic这套的一个红外接收驱动的宏观框架。

amlogic红外接收宏观框架介绍

对于amlogic的这套驱动,我目前看下来他主要是通过中断+定时器+input子系统这么一个大的结构来完成的。当我们有红外发送过来的时候,我们的红外接收头会出现一个中断,我们的Linux驱动在中断内进行按键红外码的解析以及上报,这里上报的主要是按键按下事件

而对于按键弹起事件,则通过定时器来完成,amlogic内部设立了一个一定时间的定时器,当在一定时间过后收不到一个红外的按键,就认为当前按键已经弹起,由此通过input子系统将按键弹起的事件进行上报

了解完这个宏观框架之后,正式开始代码的介绍

probe注册函数

首先,我们从probe函数来查看这个驱动的一个大概的情况

static int remote_probe(struct platform_device *pdev)
{
    	struct remote_dev *dev;
    	int ret;
    	struct remote_chip *chip;
    
    	pr_info("%s: remote_probe\n", DRIVER_NAME);
	chip = kzalloc(sizeof(struct remote_chip), GFP_KERNEL);
	 if (!chip) {
		pr_err("%s: kzalloc remote_chip error!\n", DRIVER_NAME);
		ret = -ENOMEM;
		goto err_end;
    	}

 	// input结构体初始化 包含自旋锁 定时器 input_dev等
	dev = remote_allocate_device();
 	if (!dev) {
		pr_err("%s: kzalloc remote_dev error!\n", DRIVER_NAME);
		ret = -ENOMEM;
		goto err_alloc_remote_dev;
 	}

 	// 相关锁以及链表的初始化
	 mutex_init(&chip->file_lock);
 	spin_lock_init(&chip->slock);
 	INIT_LIST_HEAD(&chip->map_tab_head);

 	// 对应结构体状态 函数指针的一个配置
	chip->r_dev = dev;
	chip->dev = &pdev->dev;

	chip->r_dev->dev = &pdev->dev;
	chip->r_dev->platform_data = (void *)chip;
	chip->r_dev->getkeycode    = getkeycode;
	chip->r_dev->ir_report_rel = ir_report_rel;
	chip->r_dev->set_custom_code = set_custom_code;
	chip->r_dev->is_valid_custom = is_valid_custom;
	chip->r_dev->is_next_repeat  = is_next_repeat;
	chip->r_dev->max_learned_pulse = MAX_LEARNED_PULSE;
	chip->set_register_config = ir_register_default_config;
 	platform_set_drvdata(pdev, chip);

 	// 红外输入设备的一个配置初始化
 	ir_input_device_init(dev->input_device, &pdev->dev, "aml_keypad");

 	// 核心的硬件初始化 包含了中断的请求以及设备树相关信息的获取以及处理
	ret = ir_hardware_init(pdev);
 	if (ret < 0)
		goto err_hard_init;

 	// 字符设备初始化
 	ret = ir_cdev_init(chip);
 	if (ret < 0)
		goto err_cdev_init;

	dev->rc_type = chip->protocol;
 	// 完成input子系统通用事件的一个注册
	ret = remote_register_device(dev);
 	if (ret)
		goto error_register_remote;

 	// 配置唤醒功能 这里是通过中断的方式来唤醒的
	 device_init_wakeup(&pdev->dev, 1);
 	dev_pm_set_wake_irq(&pdev->dev, chip->irqno);

 	// led 红外反馈 配置了接收到红外之后的一个LED闪烁反馈功能
	 led_trigger_register_simple("rc_feedback", &dev->led_feedback);

 	// 红外学习功能
 	setup_timer(&dev->learning_done, ir_learning_done, (unsigned long)dev);
	 if (dev->demod_enable)
	       demod_init(chip);
 	INIT_DELAYED_WORK(&chip->ir_workqueue, learning_done_workqueue);
 	INIT_WORK(&chip->fifo_work, get_fifo_data_work);
	 // 设置中断亲和性,将中断绑定到指定的CPU上,这里是绑定到CPU0上 减少一些上下文切换的开销
 	irq_set_affinity(chip->irqno,cpumask_of(chip->irq_cpumask));
	 return 0;

error_register_remote:
 	ir_hardware_free(pdev);
err_cdev_init:
        remote_free_device(dev);
err_hard_init:
 	ir_cdev_free(chip);
err_alloc_remote_dev:
 	kfree(chip);
err_end:
 	return ret;
}

从probe函数里面,我们可以看到这里主要是完成了中断子系统,驱动信息配置,任务队列以及一个中断亲和性的配置。这里的话他的核心函数在于ir_hardware_init内,这个函数完成了对于红外驱动最主体的功能。我们将会围绕着这个展开,而至于其他的功能,其更多是这个的一个外围和拓展,我们后续再议

ir_hardware_init核心函数

这个函数其整体结构来看他就是一个设备树信息获取以及IR控制器配置以及中断申请的函数实现,但是在这个过程中,其完成了这个红外驱动的主体功能的实现,这里将会一个个拆解

static int ir_hardware_init(struct platform_device *pdev)
{
	 int ret;

	 struct remote_chip *chip = platform_get_drvdata(pdev);

	 if (!pdev->dev.of_node) {
		dev_err(chip->dev, "pdev->dev.of_node == NULL!\n");
		return -1;
	 }

	 // 获取设备树信息
	ret = ir_get_devtree_pdata(pdev);
	 if (ret < 0)
		return ret;
	 // 通过函数指针调用到ir_register_default_config完成了IR控制器的一个默认配置
	chip->set_register_config(chip, chip->protocol);
	 // 申请中断 这里的话是一个Tasklet的上下文来完成
	ret = request_irq(chip->irqno, ir_interrupt, IRQF_SHARED
		| IRQF_NO_SUSPEND, "keypad", (void *)chip);
 	if (ret < 0)
		goto error_irq;

	chip->irq_cpumask = 1;

 	/* 
	 * 这个Tasklet通过下面方法在这个c文件的开头完成了初始化
		// 静态注册Tasklet 用于后续IR中断处理
		DECLARE_TASKLET_DISABLED(tasklet, amlremote_tasklet, 0);
	*/
	 tasklet_enable(&tasklet);
	tasklet.data = (unsigned long)chip;

	 return 0;

error_irq:
	 dev_err(chip->dev, "request_irq error %d\n", ret);

	 return ret;

}

设备树信息获取函数ir_get_devtree_pdata

这个函数他是一个典型的设备树信息获取函数,他在这里统一完成了设备树信息的一个获取,而对于我们红外按键驱动来说,其核心主要是后面的这个获取table的操作

static int ir_get_devtree_pdata(struct platform_device *pdev)
{
 	struct resource *res_irq;
	 struct resource *res_mem;
 	resource_size_t *res_start[2];
        struct pinctrl *p;
 	int ret;
	 int value = 0;
 	unsigned char i;


 	struct remote_chip *chip = platform_get_drvdata(pdev);

        // 设备树信息获取
	ret = of_property_read_u32(pdev->dev.of_node,
			"protocol", &chip->protocol);
 	if (ret) {
		dev_err(chip->dev, "don't find the node <protocol>\n");
		chip->protocol = 1;
 	}
	 dev_info(chip->dev, "protocol = 0x%x\n", chip->protocol);

	ret = of_property_read_u32(pdev->dev.of_node,
			"led_blink", &chip->r_dev->led_blink);
 	if (ret) {
		dev_err(chip->dev, "don't find the node <led_blink>\n");
		chip->r_dev->led_blink = 0;
 	}
	 dev_info(chip->dev, "led_blink = %d\n", chip->r_dev->led_blink);

	ret = of_property_read_u32(pdev->dev.of_node,
			"led_blink_frq", &value);
 	if (ret) {
		dev_err(chip->dev, "don't find the node <led_blink_frq>\n");
		chip->r_dev->delay_on = DEFAULT_LED_BLINK_FRQ;
		chip->r_dev->delay_off = DEFAULT_LED_BLINK_FRQ;
 	} else {
		chip->r_dev->delay_off = value;
		chip->r_dev->delay_on = value;
 	}
	 dev_info(chip->dev, "led_blink_frq  = %ld\n", chip->r_dev->delay_on);

	ret = of_property_read_bool(pdev->dev.of_node, "demod_enable");
 	if (ret)
		chip->r_dev->demod_enable = 1;

	ret = of_property_read_bool(pdev->dev.of_node, "use_fifo");
 	if (ret)
		chip->r_dev->use_fifo = 1;

	ret = of_property_read_bool(pdev->dev.of_node, "auto_report");
 	if (ret)
		chip->r_dev->auto_report = 1;

	p = devm_pinctrl_get_select_default(&pdev->dev);
 	if (IS_ERR(p)) {
		dev_err(chip->dev, "pinctrl error, %ld\n", PTR_ERR(p));
		return -1;
 	}

 	// 设置对应的键值映射
	 for (i = 0; i < 2; i++) {
		res_mem = platform_get_resource(pdev, IORESOURCE_MEM, i);
		if (IS_ERR_OR_NULL(res_mem)) {
			dev_err(chip->dev, "get IORESOURCE_MEM error, %ld\n",
					PTR_ERR(p));
			return PTR_ERR(res_mem);
		}
		res_start[i] = devm_ioremap_resource(&pdev->dev, res_mem);
		chip->ir_contr[i].remote_regs = (void __iomem *)res_start[i];
 	}

	res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
 	if (IS_ERR_OR_NULL(res_irq)) {
		dev_err(chip->dev, "get IORESOURCE_IRQ error, %ld\n",
				PTR_ERR(p));
		return PTR_ERR(res_irq);
 	}

	chip->irqno = res_irq->start;

 	dev_info(chip->dev, "platform_data irq =%d\n", chip->irqno);

	ret = of_property_read_u32(pdev->dev.of_node,
				"max_frame_time", &value);
 	if (ret) {
		dev_err(chip->dev, "don't find the node <max_frame_time>\n");
		value = 200; /*default value*/
 	}

	chip->r_dev->max_frame_time = value;


 	/*create map table */
        // 红外驱动核心信息获取 获取键值映射表
	ret = get_custom_tables(pdev->dev.of_node, chip);
 	if (ret < 0)
		return -1;

 	return 0;
}

按键table的获取

这一个按键table的一个获取,其使用到了一个柔性数组的东西,很好的实现了一个信息的动态分配

static int get_custom_tables(struct device_node *node,
	struct remote_chip *chip)
{
    ..........
    // 循环遍历设备树里面的所有map
    for (index = 0; index < chip->custom_num; index++) {
                // 自动拼接并获取数据
		propname = kasprintf(GFP_KERNEL, "map%d", index);
		phandle = of_get_property(custom_maps, propname, NULL);
    .......
    /*alloc memory*/
    // 使用柔性数组 这里分配了对应的大小的单元 由此codemap可以依据结构体的大小来补充
    ptable = kzalloc(sizeof(struct ir_map_tab_list) +
				    value * sizeof(union _codemap), GFP_KERNEL);
    if (!ptable)
    	return -ENOMEM;
     
     ........
     
     // 获取customcode 这个一般都是特征标识头 用来标记是哪一款遥控器
    ret = of_property_read_u32(map, "customcode", &value);
    if (ret) {
        dev_err(chip->dev, "please config customcode item\n");
        goto err;
    }
  
      ........    
     //对应结构体
    /* 
    struct ir_map_tab {
        char custom_name[CUSTOM_NAME_LEN];
        struct cursor_codemap cursor_code;
        __u16 map_size;
        __u32 custom_code;
        __u32 release_delay;
        union _codemap codemap[0];
    };
    */
    // 柔性数组获取数组数据
    ret = of_property_read_u32_array(map,
            "keymap", (u32 *)&ptable->tab.codemap[0],
            ptable->tab.map_size);

    ......
    // 对着表格里面的按键键值进行排序 方便后面查表
    ir_scancode_sort(&ptable->tab);
    // 最后将这些获取到的数据结成一个链表
    spin_lock_irqsave(&chip->slock, flags);
    list_add_tail(&ptable->list, &chip->map_tab_head);
    spin_unlock_irqrestore(&chip->slock, flags);
    .....
}

这里我对这个函数的主体信息抽象了出来,对于这个键值映射表,他里面用到了一个我们驱动里面很好用,但是也算比较复杂的东西。柔性数组。

柔性数组

在讲解之前,我们先看看他的这个结构体

struct ir_map_tab {
        char custom_name[CUSTOM_NAME_LEN];
        struct cursor_codemap cursor_code;
        __u16 map_size;
        __u32 custom_code;
        __u32 release_delay;
        union _codemap codemap[0];
    };

可以看到在这个结构体里面,他最后是一个联合体的数组,而这个数组他只有[0],这个是一个很奇妙的点,因为像我们使用数组的话,一般来说都是会对他进行大小的分配,但是这里的话,他直接就是一个[0],然后我们结合一下我们数组的这么一个概念进行讲解和理解

首先在 C 语言中,数组是一种由相同类型元素组成的连续内存块。其常见定义如下:

int arr[5]

在上面的定义里,我们向内存申明了一段常为5个int类型的数据存储空间,这段存储空间的开始位置由arr这个指针指向。对于类似于我们常规用到的arr[0],arr[1]等操作,其核心也是我们的一个指针

其等价关系如下:

int *p = arr;
// arr[0]
int value1 = *p;
// arr[1]
int value2 = *(p + 1);

然后我们这里回到刚刚柔性数组的定义这里。这里申请的是一个codemap[0],这个的意思其实就相当于一个union _codemap * codemap.

到这里的话,我疑惑为什么需要使用数组而不是一个直接的指针来完成,因为从整体的一个来说,他们其实都是相同的。

这里的话就涉及到数组和指针的一个根本区别了

那就是数组其本质是一个地址常量而指针他是一个指向地址的一个变量,变量存在固定的大小,而常量不存在固定的大小

用我们生活化的比喻来说,那就是其实指针他更接近于一个路牌的存在,他指向一个特定的地址,我们通过他去访问到我们想要的东西

image.png

但是数组就和他不一样了,他其实更接近于我们说的空地这么一个概念,对于我们的这个柔性数组arr[0],

说的更加生活话一点的话,核心就是一个告知的功能,他就好像告诉了你,除了什么什么之外,剩下的都是我家的地盘,至于我家的地盘有多大,我家的房子有多大,就全看分配的时候分配多少了,毕竟对于我们的结构体来说,每个变量的大小都是固定的,那对于这个柔性数组的大小,就是分配的大小减去变量要用的大小了

所以基于上面的这么一个特性,内核实现了柔性数组,使用柔性数组相比使用指针,他可以做到一次分配,毕竟指针他是一个变量,他指向的地址需要再次分配,而柔性数组不同,他是一个占位符,对于他的一个地址,他是和我们分配结构体的时候结合在一起的。如此我们地址访问效率也会更快

在理解了柔性数组的概念之后,我们就可以来看看这个相关的代码分配了。

/*alloc memory*/
    // 使用柔性数组 这里分配了对应的大小的单元 由此codemap可以依据结构体的大小来补充
    ptable = kzalloc(sizeof(struct ir_map_tab_list) +
				    value * sizeof(union _codemap), GFP_KERNEL);

内存分配的代码如上,其实也很好的和我们刚刚的比喻结合了起来,就是一个剩余的概念,我们分配完了struct ir_map_tab_list之后剩下的就是我们柔性数组的,那我就可以据此来依据我的设备树定义的数量来分配对应结构体的一个数据了

联合体的意义

在前面将柔性数组的结构体里面,结构体的最后是一个联合体.这个是一个很有意思的点。这里我们深挖一个这个联合体的相关东西

union _codemap {
    struct ir_key_map {
        __u16 keycode;
        __u16 scancode;
    } map;
    __u32 code;
};

从这个联合体里面可以看到,他分别由两块构成,分别是有两个u16成员变量的结构体ir_key_map以及u32的code。

下面是联合体的一个定义

联合体(union)是C语言中一种特殊的数据类型,

所以从上面的联合体定义我们可以知道,对于联合体内不同的成员变量他们都统一使用同一片内存地址,而在这个联合体里面,他存在结构体以及u32的成员变量,由此其实也意味着,对于这块区域的数据,我们可以按照结构体和u32的方式去读写,这个有啥用呢?

这个的话我看其实主要涉及到设备树里面的map信息,这里我节选了一部分作为讲解,至于完整的信息可以查看参考资料

map_0: map_0{
			mapname = "amlogic-remote-1";
			customcode = <0xfb04>;
			release_delay = <80>;
			size  = <50>;   /*keymap size*/
			keymap = <REMOTE_KEY(0x47, KEY_0)
				REMOTE_KEY(0x13, KEY_1)
				REMOTE_KEY(0x10, KEY_2)
				REMOTE_KEY(0x11, KEY_3)
				REMOTE_KEY(0x0F, KEY_4)
				REMOTE_KEY(0x0C, KEY_5)
				REMOTE_KEY(0x0D, KEY_6)
				REMOTE_KEY(0x0B, KEY_7)
				REMOTE_KEY(0x08, KEY_8)
				REMOTE_KEY(0x09, KEY_9)
				REMOTE_KEY(0x5C, KEY_RIGHTCTRL)
    ......

这里可以看到我们的keymap内是一个数组,这个数组每一个都是由REMOTE_KEY组装起来的数据。

而这个REMOTE_KEY他是一个宏,其内容如下

#define REMOTE_KEY(scancode, keycode)\
                    ((((scancode) & 0xFFFF)<<16) | ((keycode) & 0xFFFF))

可以看到这一个宏,其本质就是将给定的scancode和keycode拼接为一个u32的数据,然后在追到这里的时候,我内心存在一个较大的疑问,那就是为什么非得把数据变成32位的,然后用of_property_read_u32_array 读取呢?明明of函数族里面是存在of_property_read_u16_array,用他的话,直接存储数据,也不用搞什么联合体,也不用特地搞一个宏来将两个数据结合起来,简单暴力,为什么要这样呢?

对于这个问题AI给出的解释如下

image.png

从AI给的解释来看,我们就可以发现这样设计的第一个直接的原因,节省空间,我们知道设备树的本质是32位对齐的,就算我们使用的数据是16位的,他也是使用32位存储,然后截取其低16位,这样的话对于我们刚刚看到的这样的数组来说,如果我用16位存储,他相比32位来说他的空间消耗是翻倍的。这个是第一点

然后就是数据管理方面的问题,我们先来看看原来的设计模式,原来的设计模式的话,我们通过REMOTE_KEY将两个按键键值结合成了一个32位的数据,这样的话其整体倾向于我们常见的一个键值对的形式,这样的话我们无论是想要查key还是value都会非常的方便,就算不是联合体,我们完全可以通过位与的方式完成。但是如果我们按照16进制的方式进行存储的话,首先,我们没有办法像原来方案一样形成排列紧密的键值对,同时为了能够对这些数据进行存储和查阅,我们需要将key和value分开,分为两个数组。然后在需要查阅的时候依据index进行遍历获取

类似于这样

keymap = <0x01 0x02 0x03>
valuemap = <KEY_1 KEY_2 KEY_3>

// 通过一个for循环进行查表找到这些key和value的对应关系
for(......)

这样无论是代码设计还是内存分配方面都非常的糟糕,所以amlogic这里直接通过宏定义将u16的数据组合成u32的数据,然后使用联合体对他们进行存储,这样当我们需要进行数据获取的时候我们可以直接通过of_property_read_u32_array 函数一次性获取完全部的数据,然后当我们需要区分key和value的时候,我们只需要按照结构体的成员操作进行即可,由于是联合体,使用的内存地址都一样,也就是数据都一样,如此我们就可以做到非常方便的进行数据获取和操作了。

最终数据获取效果展示

在使用了刚刚的柔性数组和联合体完成了整体数据获取之后,我们驱动获取到了一张链表,他整体是一个以设备树customcode为区分条件,装载了设备树内keymap各种映射键值的一个链表。其类型示例如下

image.png

自此amlogic这个红外驱动设备最复杂和伤脑筋的数据获取部分就完成了,下面主要就是我们的这个转化和上报了。先擦擦汗休息一下

image.png

虽然说看着复杂,但是这些抽象和实现能力还是非常佩服amlogic的,做的非常的好,适配的能力也很强

ir_contr_init红外控制器初始化

ir_contr_init这个函数的调用他也是在ir_hardware_init里面调用到的,他通过函数指针指向他,然后调用

chip->set_register_config = ir_register_default_config;
........
// 通过函数指针调用到ir_register_default_config完成了IR控制器的一个默认配置
chip->set_register_config(chip, chip->protocol);

而这个ir_register_default_config函数主要也是调用ir_contr_init,所以我们直接看这个

int ir_register_default_config(struct remote_chip *chip, int type)
{
    if (ENABLE_LEGACY_IR(type)) {
        /*initialize registers for legacy IR controller*/
        ir_contr_init(chip, LEGACY_IR_TYPE_MASK(type), LEGACY_IR_ID);
    } else {
        /*disable legacy IR controller: REG_REG1[15]*/
        remote_reg_write(chip, LEGACY_IR_ID, REG_REG1, 0x0);
    }
    /*initialize registers for Multi-format IR controller*/
    ir_contr_init(chip, MULTI_IR_TYPE_MASK(type), MULTI_IR_ID);
    
    return 0;
    
}

首先,对于这个函数的意义,如同他的名字一样,ir_contr_init,ir的控制器初始化,在这里面,amlogic依据使用的协议,按需对这个硬件对于的寄存器进行操作,然后写入对于的数据,这个过程就是我们常用的外设初始化的一个过程了。在本次驱动讲解里面,对于这些寄存器地址相关的操作,我们不会详细讲,这个主要看芯片手册,本次我主要也是关注他的流程和对于的代码抽象亮点。我们先看看这个代码

// 真正的IR硬件初始化 依据配置完成了不同的协议类型的寄存器配置和函数指针赋值
static int ir_contr_init(struct remote_chip *chip, int type, unsigned char id)
{
    // 这个是一个指向协议类型数组的指针 通过协议类型来找到对应的寄存器配置以及函数指针等信息
    const struct aml_remote_reg_proto **reg_proto = remote_reg_proto;
    struct remote_reg_map *reg_map;
    int size;
    int status;
    
    // 找到对应的协议类型
    for ( ; (*reg_proto) != NULL ; ) {
        if ((*reg_proto)->protocol == type)
            break;
        reg_proto++;
    }
    
    .........
    //忽略了一部分寄存器的操作
    // 配置项装载
    reg_map = (*reg_proto)->reg_map;
    size    = (*reg_proto)->reg_map_size;
    
    // 将给定的配置reg map里面的寄存器值写入到对应的硬件寄存器里面
    for (  ; size > 0;  ) {
        remote_reg_write(chip, id, reg_map->reg, reg_map->val);
        dev_info(chip->dev, "reg=0x%x, val=0x%x\n",
        reg_map->reg, reg_map->val);
        reg_map++;
        size--;
    }
    
    .........
    // 对应协议类型的函数指针赋值
    chip->ir_contr[id].get_scancode      = (*reg_proto)->get_scancode;
    chip->ir_contr[id].get_decode_status = (*reg_proto)->get_decode_status;
    chip->ir_contr[id].proto_name        = (*reg_proto)->name;
    chip->ir_contr[id].get_custom_code   = (*reg_proto)->get_custom_code;
    chip->ir_contr[id].set_custom_code   = (*reg_proto)->set_custom_code;
    chip->ir_contr[id].protocol_delay   = (*reg_proto)->protocol_delay;

可以看到,这里的核心实现主要是一个数据的装载,他首先依据我们设备树里面写的协议,找到对应的协议,然后加载其内部的一个数据。整体逻辑的话比较简单

这里我们主要来看看这个reg_proto的这么一个抽象,reg_proto这里是一个二级指针,他这里也是为了能够更好地利用设备树而做出的抽象处理,也是一个非常好的素材,我们也一起看看

首先reg_proto指向的对象他是一个数组,这个数组里面存储了amlogic设定的多种协议的类型的结构体

const struct aml_remote_reg_proto *remote_reg_proto[] = {
    &reg_nec,
    &reg_duokan,
    &reg_xmp_1,
    &reg_xmp_1_sw,
    &reg_nec_sw,
    &reg_rc5,
    &reg_rc6,
    &reg_legacy_nec,
    &reg_toshiba,
    &reg_rca,
    NULL
};

这里抽出我们本次关注的重点协议NEC来看一下

static struct aml_remote_reg_proto reg_nec = {
    .protocol = REMOTE_TYPE_NEC,
    .name     = "NEC",
    .protocol_delay = 40,
    .reg_map      = regs_default_nec,
    .reg_map_size = ARRAY_SIZE(regs_default_nec),
    .get_scancode      = ir_nec_get_scancode,
    .get_decode_status = ir_nec_get_decode_status,
    .get_custom_code   = ir_nec_get_custom_code
};

这里的protocol其实就是一个宏定义,他对应的是一个数值,我们通过他来判断协议是否一样

image.png

这里的reg_map就是在上面函数加载的时候用到的一个加载键值对。他通过数组独立配对,每个不同的协议结构体不一样,由此保证了各个协议结构体的一个加载的易读性和方便

static struct remote_reg_map regs_default_nec[] = {
    { REG_LDR_ACTIVE,   (500 << 16) | (400 << 0)},
    { REG_LDR_IDLE,     300 << 16 | 200 << 0},
    { REG_LDR_REPEAT,   150 << 16 | 80 << 0},
    { REG_BIT_0,        72 << 16 | 40 << 0},
    { REG_REG0,         7 << 28 | (0xDAC << 12) | 0x13},
    { REG_STATUS,       (134 << 20) | (90 << 10)},
    { REG_REG1,         0x9f00},
    { REG_REG2,         0x00},
    { REG_DURATN2,      0x00},
    { REG_DURATN3,      0x00}
};

在需要读取写入的时候,通过键值对读取和写入

// 将给定的配置reg map里面的寄存器值写入到对应的硬件寄存器里面
    for (  ; size > 0;  ) {
        remote_reg_write(chip, id, reg_map->reg, reg_map->val);
        dev_info(chip->dev, "reg=0x%x, val=0x%x\n",
        reg_map->reg, reg_map->val);
        reg_map++;
        size--;
    }

到这里我们的所有重要的数据加载逻辑都完成了,下面是处理的核心逻辑,就我整体看下来的话,其实核心逻辑并不算复杂,也不难,他的问题点也大多是这些数据加载,怎么利用设备树后续能够更好地维护和对这些需要写入的不同协议的数据结构体的一个封装,其实这里才是amlogic下功夫很深的地方,也是我们作为一个好的驱动开发工程师需要注意的地方,抽象抽象再抽象。下面开始按键的核心逻辑的一个介绍

按键驱动实现核心逻辑介绍

按键按下事件上报流程

按键实现的核心处理逻辑如下,主要就是一个中断实现。

ret = request_irq(chip->irqno, ir_interrupt, IRQF_SHARED
		| IRQF_NO_SUSPEND, "keypad", (void *)chip);

对于这个中断实现,amlogic通过tasklet实现了中断的下半段操作。我们先来看看上半段做了什么

static irqreturn_t ir_interrupt(int irq, void *dev_id){
    .......
    // 这里通过读取对应的寄存器的来完成实现
    remote_reg_read(rc, MULTI_IR_ID, REG_REG1, &val);
    val = (val & 0x1FFF0000) >> 16;
    sprintf(buf, "duration:%d\n", val);
    debug_log_printk(rc->r_dev, buf);
    .......
    // 红外学习功能?
    if (r_dev->ir_learning_on && !r_dev->ir_learning_done) {
        .......
    }
    
    // 软件解码?
    if (MULTI_IR_SOFTWARE_DECODE(rc->protocol)) {
        ........
    // 正常硬件解码
    } else {
        for (cnt = 0; cnt < (ENABLE_LEGACY_IR(rc->protocol)
                ? 2:1); cnt++) {
            // 这里是为了确认是哪一个控制器产生的中断 因为有些芯片是双IR控制器的
            remote_reg_read(rc, cnt, REG_STATUS, &contr_status);
            if (IR_DATA_IS_VALID(contr_status)) {
                // 增加有效计数
                rc->ir_work = cnt;
                break;
            }
        }
        
        if (cnt == IR_ID_MAX) {
            dev_err(rc->dev, "invalid interrupt.\n");
            return IRQ_HANDLED;
        }
        
        // 下半段调度
        tasklet_schedule(&tasklet);
    }
}

从上半段的代码可以知道,上半段其实主要是做一些功能的检测,比如是否是红外学习功能,软解码。对于我们最常用到的硬件解码,他主要就是进行一个有效信息的获取,看看有多少红外数据是有效的,然后就调度到下半段了

下半段他的函数主要是关联到了amlremote_tasklet

// 静态注册Tasklet 用于后续IR中断处理
DECLARE_TASKLET_DISABLED(tasklet, amlremote_tasklet, 0);

对应的tasklet实现如下

static void amlremote_tasklet(unsigned long data){
    .........
    /**
    *need first get_scancode, then get_decode_status, the status
    *may was set flag from get_scancode function
    */
    // 临界区中断保护 避免在处理的时候再次中断而导致数据异常
    spin_lock_irqsave(&chip->slock, flags);
    if (chip->ir_contr[chip->ir_work].get_scancode)
        scancode = chip->ir_contr[chip->ir_work].get_scancode(chip);
    if (chip->ir_contr[chip->ir_work].get_decode_status)
        status = chip->ir_contr[chip->ir_work].get_decode_status(chip);
    // 检查引导码状态
    if (status == REMOTE_NORMAL) {
        remote_dbg(chip->dev, "receive scancode=0x%x\n", scancode);
        remote_keydown(chip->r_dev, scancode, status);
    } else if (status & REMOTE_REPEAT) {
        remote_dbg(chip->dev, "receive repeat\n");
        remote_keydown(chip->r_dev, scancode, status);
    } else
        dev_err(chip->dev, "receive error %d\n", status);
    spin_unlock_irqrestore(&chip->slock, flags);
}

在这里,我们开始完成红外码的一个读取的工作了,在前面我们已经分析了这个get_scancode的一个对于的函数,这里我们直接打开我们的nec相关函数看看情况

static int ir_nec_get_scancode(struct remote_chip *chip)
{
    int  code = 0;
    int decode_status = 0;
    int status = 0;
    char custom_buf[50];
    
    // 首先读取引导码的状态
    remote_reg_read(chip, MULTI_IR_ID, REG_STATUS, &decode_status);
    if (decode_status & 0x01)
        status |= REMOTE_REPEAT;
    chip->decode_status = status; /*set decode status*/
    // 然后直接读取整个帧数据
    // 这里由于我们是硬件解码的,所以我们不需要手动对帧进行解码校验,直接读取帧数据就可以了
    remote_reg_read(chip, MULTI_IR_ID, REG_FRAME, &code);
    remote_dbg(chip->dev, "MULTI framecode=0x%x\n", code);
    sprintf(custom_buf, "0x%x\n", code);
    if (code != 0)
        customcode_log_printk(chip->r_dev, custom_buf);
    // 我们这里得到的帧数据是由地址码和数据码结合起来的一个32位数据,我们将原始的码值存储起来
    chip->r_dev->cur_hardcode = code;
    // 然后将数据码部分取出来作为最终的码值返回
    code = (code >> 16) & 0xff;
    return code;
}

由于已经硬件解码完了,所以我们的数据读取部分其实也简单了很多,我们只需要把芯片解码完成的数据直接提取出来即可。上面代码里面注释的很详细了,就不多做介绍了。

在我们上面读取完scancode之后,我们还需要去看看引导码的情况,看看这一次是重复码还是说啥,而这个功能的实现也很简单,就是直接看我们在get_scancode时候存储的变量就好了

static int ir_nec_get_decode_status(struct remote_chip *chip)
{
    int status = chip->decode_status;
    return status;
}

在完成了上面的操作之后,驱动就会按照引导码的一个情况,进行上报

// 检查引导码状态
    if (status == REMOTE_NORMAL) {
        remote_dbg(chip->dev, "receive scancode=0x%x\n", scancode);
        remote_keydown(chip->r_dev, scancode, status);
    } else if (status & REMOTE_REPEAT) {
        remote_dbg(chip->dev, "receive repeat\n");
        remote_keydown(chip->r_dev, scancode, status);
    }

对于remote_keydown他的功能主要是对按键事件进行处理,如果配置led的亮灭,然后开始进行一些预查表操作,然后将查出来的keycode上传上去,同时配置相关按键定时器,用来检测按键的弹起状态,这个函数会是底层处理里面比较重要的一个函数,在下面的话就是通过input子系统直接上传上去了。所以这个函数我会着重讲一下。

void remote_keydown(struct remote_dev *dev, int scancode, int status)
{
    .........
    // 如果使能了LED闪烁功能 就在这里触发LED闪烁
    if (dev->led_blink)
        led_trigger_blink_oneshot(dev->led_feedback, &dev->delay_on,
                &dev->delay_off, 0);
    
    // 如果不是重复按键 则开始做按键的查表操作
    // 这里比较隐蔽
    if (status != REMOTE_REPEAT) {
        if (dev->is_valid_custom &&
        (false == dev->is_valid_custom(dev)))
            dev_err(dev->dev, "invalid custom:0x%x\n",
            dev->cur_hardcode);
    }
    spin_lock_irqsave(&dev->keylock, flags);
    ...........
    
    // 如果是正常按键事件 就进行按键键值转化并上报
    if (status == REMOTE_NORMAL) {
        keycode = dev->getkeycode(dev, scancode);
        
        // 对于power按键他比较特殊一点,需要能唤醒系统
        if (keycode == KEY_POWER)
            pm_stay_awake(dev->dev);
        // 完成按键事件上报
        ir_do_keydown(dev, scancode, keycode);
    }
    
    // 已经完成处理的按键对相关变量进行处理 并且开始设置定时器 用于查看按键是否弹起
    if (dev->keypressed) {
        dev->wait_next_repeat = 0;
        /* used for NEC like protocol with data-->repeat */
        if (protocol_delay && (status != REMOTE_NORMAL)) {
            dev->keyup_jiffies = jiffies +
            	msecs_to_jiffies(protocol_delay);
        } else {
            dev->keyup_jiffies = jiffies +
        	        msecs_to_jiffies(dev->keyup_delay);
        }
        // 启动定时器
        mod_timer(&dev->timer_keyup, dev->keyup_jiffies);
    }
    spin_unlock_irqrestore(&dev->keylock, flags);
}

首先是is_valid_custom这个函数,其实一开始我没有留意到他,因为他这个函数看着就是一个判断当前的数据是否合法的操作(光从名字上看是这么理解),但没想到他这里还顺表完成了查表和对于表头的配置的操作。这个对于后面的操作是有很大的作用的。

// 这个函数将会从红外码的红外头里面获取到对应的customcode 这个customcode一般是特征标识头 用来标记是哪一款遥控器
// 然后就会根据这个customcode去之前形成的链表里面去寻找对应的键值映射表 以供后续的按键事件处理
static bool is_valid_custom(struct remote_dev *dev)
{
    struct remote_chip *chip = (struct remote_chip *)dev->platform_data;
    int custom_code;
    
    // 获取对应协议的custom_code
    if (!chip->ir_contr[chip->ir_work].get_custom_code)
        return true;
    custom_code = chip->ir_contr[chip->ir_work].get_custom_code(chip);
    // 查表然后装载对应的表头
    chip->cur_tab = seek_map_tab(chip, custom_code);
    if (chip->cur_tab) {
        dev->keyup_delay = chip->cur_tab->tab.release_delay;
        return true;
    }
    return false;
}

从上面的函数可以知道,他这里其实就做了2个操作,一个get_custom_code获取对于的code,然后就是拿这个区查表seek_map_tab

// 获取之前解码出来数值的高16位 也就是地址码了
static u32 ir_nec_get_custom_code(struct remote_chip *chip)
{
    u32 custom_code;
    
    custom_code = chip->r_dev->cur_hardcode & 0xffff;
    return custom_code;
}

查表操作也很简单,就是看这个customcode对应的表是哪一个,这个其实就是看我们设备树里面的那一个map和他能够对的上

// 查询之前构造的链表里面的custom code 来确认当前的遥控器对应的键值映射表
struct ir_map_tab_list *seek_map_tab(struct remote_chip *chip, int custom_code)
{
    struct ir_map_tab_list *ir_map = NULL;
    
    list_for_each_entry(ir_map, &chip->map_tab_head, list) {
        if (ir_map->tab.custom_code == custom_code)
            return ir_map;
    }
    return NULL;
}
map_0: map_0{
			mapname = "amlogic-remote-1";
			// 看的就是这个东西
			customcode = <0xfb04>;
			release_delay = <80>;

获取到表头信息之后就会在下面的函数里面开始获取这个物理码对应的Linux键值码

if (status == REMOTE_NORMAL) {
		keycode = dev->getkeycode(dev, scancode);

		if (keycode == KEY_POWER)
			pm_stay_awake(dev->dev);
		// 完成按键事件上报
		ir_do_keydown(dev, scancode, keycode);
	}

这个getkeycode的核心逻辑如下:

static u32 getkeycode(struct remote_dev *dev, u32 scancode) {
    .......
    // 通过物理码获取对应的按键键值的下标
    index = ir_lookup_by_scancode(&ct->tab, scancode);
    ......
    // 返回对应的键值
    return ct->tab.codemap[index].map.keycode;
}

对于这个查表的操作,其实也很简单,他利用了我们之前在构造链表内部这些按键映射关系的时候排序形成的有序表,然后直接利用二分查找来找到他,

// 这里利用红外头来确认了是那个表之后 使用二分来查找这个红外键值对应的keycode
static int ir_lookup_by_scancode(struct ir_map_tab *ir_map,
					  unsigned int scancode)
{
    int start = 0;
    int end = ir_map->map_size - 1;
    int mid;
    
    while (start <= end) {
        mid = (start + end) >> 1;
        if (ir_map->codemap[mid].map.scancode < scancode)
            start = mid + 1;
        else if (ir_map->codemap[mid].map.scancode > scancode)
            end = mid - 1;
        else
            return mid;
    }
    
    return -1;
}

排序键值位有序表的操作在这里,我就不展开说了

// 对里面的scancode进行排序,方便后续的二分查找
int ir_scancode_sort(struct ir_map_tab *ir_map)

同样的,键值的返回也是利用对应的index的结构体里面keycode对应的成员变量完成。这里在直接介绍联合体的时候有着重说过,这里不做赘述

    // 返回对应的键值
    return ct->tab.codemap[index].map.keycode;

拿到了按键keycode之后就是利用input子系统上报了。这里比较简单

static void ir_do_keydown(struct remote_dev *dev, int scancode,
			  u32 keycode){
    ........
    // 利用子系统上报按键按下事件
    dev->keypressed = true;
    dev->last_scancode = scancode;
    dev->last_keycode = keycode;
    input_report_key(dev->input_device, keycode, 1);
    input_sync(dev->input_device);              
 }

按键弹起事件流程

在看完了按键按下事件之后,我们来看一下按键弹起事件的一个检测,他流程大体一样,只是说用定时器来完成

按键弹起事件的流程,和我们按下都是处于一个流程,他都是在我们中断的时候开始的初始化

他主要是在我们remote_keydown的时候初始化一个计时器,然后查看其是否到了时间,时间到了就会开始上报按键

void remote_keydown(struct remote_dev *dev, int scancode, int status) {
    ........
    // 配置对应的定时器超时时间,超时时间可以通过设备树配置
    dev->wait_next_repeat = 0;
    /* used for NEC like protocol with data-->repeat */
    if (protocol_delay && (status != REMOTE_NORMAL)) {
    dev->keyup_jiffies = jiffies +
    	msecs_to_jiffies(protocol_delay);
    } else {
    dev->keyup_jiffies = jiffies +
    	msecs_to_jiffies(dev->keyup_delay);
    }
    // 启动定时器
    mod_timer(&dev->timer_keyup, dev->keyup_jiffies);
}

下面让我们看看这个定时器对应的处理函数是什么

其对应的定时器设置如下,在下面的这个函数完成

struct remote_dev *remote_allocate_device(void){
    ........
    // 设置定时器 指定定时器回调函数以及参数
    setup_timer(&dev->timer_keyup, ir_timer_keyup, (unsigned long)dev);
    .........
}

函数实现如下

static void ir_timer_keyup(unsigned long cookie)
{
        ........
        // 定时器时间到了
        if (time_is_before_eq_jiffies(dev->keyup_jiffies))
        // 进行红外上报处理
            ir_do_keyup(dev);
        dev->wait_next_repeat = 0;
    }
    spin_unlock_irqrestore(&dev->keylock, flags);
}

这个函数比我们按下的时候更加简单,就是将之前的那个按键进行一个上报

static void ir_do_keyup(struct remote_dev *dev) {
    input_report_key(dev->input_device, dev->last_keycode, 0);
    input_sync(dev->input_device);
    if (dev->last_keycode == KEY_POWER)
        pm_relax(dev->dev);
    dev->keypressed = false;
    dev->last_scancode = -1;
    remote_dbg(dev->dev, "keyup!!\n");
}

小结

自此我们amlogic的红外按键驱动的整体流程就梳理完成了,这个东西从看到我完完全全写下来,陆陆续续花了我大概5个工作日左右,其实对于整体的红外驱动来说,他的数据读取是很简单的,比如说硬件解码的,他就是直接读取对应的寄存器的值就好了,然后上报的话其实也是利用Linux的input子系统来完成,但是的话其实麻烦点都是在查表的这么一个流程这里,我们红外驱动需要完成对红外物理码到Linux键值码的一个转化,所以就需要做很多表格装载和表格查询的一个东西,这里amlogic的最主要抽象也就是在这里,为了一个更好地拓展性,他利用到了柔性数组和联合体来对这些数据进行装载,而为了查表利用到了二分查找等相关的一个操作。其实这个驱动对于我们更多的是在数据结构方面的一个学习和深入,还有说Linux的一些内存相关的特性知识的学习和巩固。至于红外驱动的细节,因为我们没有芯片数据手册,所以相关的细节我们做了一些忽略,但是这个对于我们学习Linux驱动框架都是很好的示例

参考资料

amlogic红外驱动的DTS相关代码块

	remote:rc@0xff808040 {
		compatible = "amlogic, aml_remote";
		dev_name = "meson-remote";
		reg = <0xff808040 0x44>, /*Multi-format IR controller*/
			<0xff808000 0x20>; /*Legacy IR controller*/
		status = "okay";
		protocol = <REMOTE_TYPE_NEC>;
		led_blink = <1>;
		led_blink_frq = <100>;
		interrupts = <0 196 1>;
		pinctrl-names = "default";
		pinctrl-0 = <&remote_pins>;
		map = <&custom_maps>;
		max_frame_time = <200>; /*set software decoder max frame time*/
	};

	custom_maps:custom_maps {
		mapnum = <9>;
		map0 = <&map_0>;
		map1 = <&map_1>;
		map2 = <&map_2>;
		map3 = <&map_3>;
		map4 = <&map_4>;
		map5 = <&map_5>;
		map6 = <&map_6>;
		map7 = <&map_7>;
		map8 = <&map_8>;
		map_0: map_0{
			mapname = "amlogic-remote-1";
			customcode = <0xfb04>;
			release_delay = <80>;
			size  = <50>;   /*keymap size*/
			keymap = <REMOTE_KEY(0x47, KEY_0)
				REMOTE_KEY(0x13, KEY_1)
				REMOTE_KEY(0x10, KEY_2)
				REMOTE_KEY(0x11, KEY_3)
				REMOTE_KEY(0x0F, KEY_4)
				REMOTE_KEY(0x0C, KEY_5)
				REMOTE_KEY(0x0D, KEY_6)
				REMOTE_KEY(0x0B, KEY_7)
				REMOTE_KEY(0x08, KEY_8)
				REMOTE_KEY(0x09, KEY_9)
				REMOTE_KEY(0x5C, KEY_RIGHTCTRL)
				REMOTE_KEY(0x51, KEY_F3)
				REMOTE_KEY(0x50, KEY_F4)
				REMOTE_KEY(0x40, KEY_F5)
				REMOTE_KEY(0x4d, KEY_F6)
				REMOTE_KEY(0x43, KEY_F7)
				REMOTE_KEY(0x17, KEY_F8)
				REMOTE_KEY(0x00, KEY_F9)
				REMOTE_KEY(0x01, KEY_F10)
				REMOTE_KEY(0x16, KEY_F11)
				REMOTE_KEY(0x49, KEY_BACKSPACE)
				REMOTE_KEY(0x06, KEY_PROPS)
				REMOTE_KEY(0x14, KEY_UNDO)
				REMOTE_KEY(0x44, KEY_UP)
				REMOTE_KEY(0x1D, KEY_DOWN)
				REMOTE_KEY(0x1C, KEY_LEFT)
				REMOTE_KEY(0x48, KEY_RIGHT)
				REMOTE_KEY(0x53, KEY_LEFTMETA)
				REMOTE_KEY(0x45, KEY_PAGEUP)
				REMOTE_KEY(0x19, KEY_PAGEDOWN)
				REMOTE_KEY(0x52, KEY_PAUSE)
				REMOTE_KEY(0x05, KEY_HANGEUL)
				REMOTE_KEY(0x59, KEY_HANJA)
				REMOTE_KEY(0x1b, KEY_SCALE)
				REMOTE_KEY(0x04, KEY_KPCOMMA)
				REMOTE_KEY(0x1A, KEY_POWER)
				REMOTE_KEY(0x0A, KEY_TAB)
				REMOTE_KEY(0x0e, KEY_MUTE)
				REMOTE_KEY(0x1F, KEY_HOME)
				REMOTE_KEY(0x1e, KEY_FRONT)
				REMOTE_KEY(0x07, KEY_COPY)
				REMOTE_KEY(0x12, KEY_OPEN)
				REMOTE_KEY(0x54, KEY_PASTE)
				REMOTE_KEY(0x02, KEY_FIND)
				REMOTE_KEY(0x4f, KEY_A)
				REMOTE_KEY(0x42, KEY_B)
				REMOTE_KEY(0x5d, KEY_C)
				REMOTE_KEY(0x4c, KEY_D)
				REMOTE_KEY(0x58, KEY_CUT)
				REMOTE_KEY(0x55, KEY_CALC)>;
		};
		map_1: map_1{
			mapname = "amlogic-remote-2";
			customcode = <0xfe01>;
			release_delay = <80>;
			size  = <53>;
			keymap = <REMOTE_KEY(0x01, KEY_1)
				REMOTE_KEY(0x02, KEY_2)
				REMOTE_KEY(0x03, KEY_3)
				REMOTE_KEY(0x04, KEY_4)
				REMOTE_KEY(0x05, KEY_5)
				REMOTE_KEY(0x06, KEY_6)
				REMOTE_KEY(0x07, KEY_7)
				REMOTE_KEY(0x08, KEY_8)
				REMOTE_KEY(0x09, KEY_9)
				REMOTE_KEY(0x0a, KEY_0)
				REMOTE_KEY(0x1F, KEY_FN_F1)
				REMOTE_KEY(0x15, KEY_MENU)
				REMOTE_KEY(0x16, KEY_TAB)
				REMOTE_KEY(0x0c, KEY_CHANNELUP)
				REMOTE_KEY(0x0d, KEY_CHANNELDOWN)
				REMOTE_KEY(0x0e, KEY_VOLUMEUP)
				REMOTE_KEY(0x0f, KEY_VOLUMEDOWN)
				REMOTE_KEY(0x11, KEY_HOME)
				REMOTE_KEY(0x1c, KEY_RIGHT)
				REMOTE_KEY(0x1b, KEY_LEFT)
				REMOTE_KEY(0x19, KEY_UP)
				REMOTE_KEY(0x1a, KEY_DOWN)
				REMOTE_KEY(0x1d, KEY_ENTER)
				REMOTE_KEY(0x17, KEY_MUTE)
				REMOTE_KEY(0x49, KEY_FINANCE)
				REMOTE_KEY(0x43, KEY_BACK)
				REMOTE_KEY(0x12, KEY_FN_F4)
				REMOTE_KEY(0x14, KEY_FN_F5)
				REMOTE_KEY(0x18, KEY_FN_F6)
				REMOTE_KEY(0x59, KEY_INFO)
				REMOTE_KEY(0x5a, KEY_STOPCD)
				REMOTE_KEY(0x10, KEY_POWER)
				REMOTE_KEY(0x42, KEY_PREVIOUSSONG)
				REMOTE_KEY(0x44, KEY_NEXTSONG)
				REMOTE_KEY(0x1e, KEY_REWIND)
				REMOTE_KEY(0x4b, KEY_FASTFORWARD)
				REMOTE_KEY(0x58, KEY_PLAYPAUSE)
				REMOTE_KEY(0x46, KEY_PROPS)
				REMOTE_KEY(0x40, KEY_UNDO)
				REMOTE_KEY(0x38, KEY_SCROLLLOCK)
				REMOTE_KEY(0x57, KEY_FN)
				REMOTE_KEY(0x5b, KEY_FN_ESC)
				REMOTE_KEY(0x54, KEY_RED)
				REMOTE_KEY(0x4c, KEY_GREEN)
				REMOTE_KEY(0x4e, KEY_YELLOW)
				REMOTE_KEY(0x55, KEY_BLUE)
				REMOTE_KEY(0x53, KEY_BLUETOOTH)
				REMOTE_KEY(0x52, KEY_WLAN)
				REMOTE_KEY(0x39, KEY_CAMERA)
				REMOTE_KEY(0x41, KEY_SOUND)
				REMOTE_KEY(0x0b, KEY_QUESTION)
				REMOTE_KEY(0x00, KEY_CHAT)
				REMOTE_KEY(0x13, KEY_SEARCH)>;
		};
		map_2: map_2{
			mapname = "amlogic-remote-3";
			customcode = <0xbd02>;
			release_delay = <80>;
			size  = <17>;
			keymap = <REMOTE_KEY(0xca,103)
			REMOTE_KEY(0xd2,108)
			REMOTE_KEY(0x99,105)
			REMOTE_KEY(0xc1,106)
			REMOTE_KEY(0xce,97)
			REMOTE_KEY(0x45,116)
			REMOTE_KEY(0xc5,133)
			REMOTE_KEY(0x80,113)
			REMOTE_KEY(0xd0,15)
			REMOTE_KEY(0xd6,125)
			REMOTE_KEY(0x95,102)
			REMOTE_KEY(0xdd,104)
			REMOTE_KEY(0x8c,109)
			REMOTE_KEY(0x89,131)
			REMOTE_KEY(0x9c,130)
			REMOTE_KEY(0x9a,120)
			REMOTE_KEY(0xcd,121)>;
		};

		map_3: map_3{
			mapname = "amlogic-remote-4";
			customcode = <0x1104>;
			release_delay = <65>;
			size  = <26>;
			keymap = <REMOTE_KEY(0x2a,116)
				REMOTE_KEY(0x2b,113)
				REMOTE_KEY(0x09,141)
				REMOTE_KEY(0x08,102)
				REMOTE_KEY(0x05,97)
				REMOTE_KEY(0x01,103)
				REMOTE_KEY(0x02,108)
				REMOTE_KEY(0x03,105)
				REMOTE_KEY(0x04,106)
				REMOTE_KEY(0x07,158)
				REMOTE_KEY(0x0c,115)
				REMOTE_KEY(0x0e,402)
				REMOTE_KEY(0x0d,114)
				REMOTE_KEY(0x0f,403)
				REMOTE_KEY(0x10,11)
				REMOTE_KEY(0x11,2)
				REMOTE_KEY(0x12,3)
				REMOTE_KEY(0x13,4)
				REMOTE_KEY(0x14,5)
				REMOTE_KEY(0x15,6)
				REMOTE_KEY(0x16,7)
				REMOTE_KEY(0x17,8)
				REMOTE_KEY(0x18,9)
				REMOTE_KEY(0x19,10)
				REMOTE_KEY(0x1a,609)
				REMOTE_KEY(0x1b,610)>;
		};

		map_4: map_4{
			mapname = "amlogic-remote-5";
			customcode = <0xff00>;
			release_delay = <65>;
			size  = <32>;
			keymap = <REMOTE_KEY(0xdc, KEY_POWER)
				REMOTE_KEY(0x9c, KEY_MUTE)
				REMOTE_KEY(0xcd, KEY_F6)
				REMOTE_KEY(0x91, KEY_F7)
				REMOTE_KEY(0x85, 104)		/*CHANNEL_UP*/
				REMOTE_KEY(0x86, 109)		/*CHANNEL_DOWN*/
				REMOTE_KEY(0x81, KEY_VOLUMEDOWN)
				REMOTE_KEY(0x80, KEY_VOLUMEUP)
				REMOTE_KEY(0x8d, 176)		/*SETTING*/
				REMOTE_KEY(0x82, 127)		/*MENU*/
				REMOTE_KEY(0x88, 172)		/*Home*/
				REMOTE_KEY(0x95, KEY_BACK)
				REMOTE_KEY(0x99, KEY_LEFT)
				REMOTE_KEY(0xca, KEY_UP)
				REMOTE_KEY(0xc1, KEY_RIGHT)
				REMOTE_KEY(0xd2, KEY_DOWN)
				REMOTE_KEY(0xce, KEY_RIGHTCTRL)
				REMOTE_KEY(0x92, KEY_1)
				REMOTE_KEY(0x93, KEY_2)
				REMOTE_KEY(0xcc, KEY_3)
				REMOTE_KEY(0x8e, KEY_4)
				REMOTE_KEY(0x8f, KEY_5)
				REMOTE_KEY(0xc8, KEY_6)
				REMOTE_KEY(0x8a, KEY_7)
				REMOTE_KEY(0x8b, KEY_8)
				REMOTE_KEY(0xc4, KEY_9)
				REMOTE_KEY(0xda, KEY_NUMERIC_POUND)
				REMOTE_KEY(0x87, KEY_0)
				REMOTE_KEY(0xcb, KEY_DELETE)
				REMOTE_KEY(0xf0, 0x1f4)		/*M*/
				REMOTE_KEY(0xd0, KEY_NUMERIC_STAR)
				REMOTE_KEY(0xff, KEY_BLUETOOTH)>;
		};

		map_5: map_5{
			mapname = "amlogic-remote-6";
			customcode = <0xdd22>;
			release_delay = <65>;
			size  = <32>;
			keymap = <REMOTE_KEY(0xdc, KEY_POWER)
				REMOTE_KEY(0x9c, KEY_MUTE)
				REMOTE_KEY(0xcd, KEY_F6)
				REMOTE_KEY(0x91, KEY_F7)
				REMOTE_KEY(0x85, 104)		/*CHANNEL_UP*/
				REMOTE_KEY(0x86, 109)		/*CHANNEL_DOWN*/
				REMOTE_KEY(0x81, KEY_VOLUMEDOWN)
				REMOTE_KEY(0x80, KEY_VOLUMEUP)
				REMOTE_KEY(0x8d, 176)		/*SETTING*/
				REMOTE_KEY(0x82, 127)		/*MENU*/
				REMOTE_KEY(0x88, 172)		/*Home*/
				REMOTE_KEY(0x95, KEY_BACK)
				REMOTE_KEY(0x99, KEY_LEFT)
				REMOTE_KEY(0xca, KEY_UP)
				REMOTE_KEY(0xc1, KEY_RIGHT)
				REMOTE_KEY(0xd2, KEY_DOWN)
				REMOTE_KEY(0xce, KEY_RIGHTCTRL)
				REMOTE_KEY(0x92, KEY_1)
				REMOTE_KEY(0x93, KEY_2)
				REMOTE_KEY(0xcc, KEY_3)
				REMOTE_KEY(0x8e, KEY_4)
				REMOTE_KEY(0x8f, KEY_5)
				REMOTE_KEY(0xc8, KEY_6)
				REMOTE_KEY(0x8a, KEY_7)
				REMOTE_KEY(0x8b, KEY_8)
				REMOTE_KEY(0xc4, KEY_9)
				REMOTE_KEY(0xda, KEY_NUMERIC_POUND)
				REMOTE_KEY(0x87, KEY_0)
				REMOTE_KEY(0xcb, KEY_DELETE)
				REMOTE_KEY(0xf0, 0x1f4)		/*M*/
				REMOTE_KEY(0xd0, KEY_NUMERIC_STAR)
				REMOTE_KEY(0xff, KEY_BLUETOOTH)>;
		};

		map_6: map_6{
			mapname = "amlogic-remote-7";
			customcode = <0x4cb3>;
			release_delay = <65>;
			size  = <38>;
			keymap = <REMOTE_KEY(0xdc, 179)
				REMOTE_KEY(0x9c, 113)
				REMOTE_KEY(0xcd, 64)
				REMOTE_KEY(0x91, 65)
				REMOTE_KEY(0x83, 66)
				REMOTE_KEY(0xc3, 67)
				REMOTE_KEY(0x86, 167)
				REMOTE_KEY(0x85, 166)
				REMOTE_KEY(0x81, 114)
				REMOTE_KEY(0x80, 115)
				REMOTE_KEY(0x8d, 176)
				REMOTE_KEY(0x82, 102)
				REMOTE_KEY(0xce, 97)
				REMOTE_KEY(0xca, 103)
				REMOTE_KEY(0xd2, 108)
				REMOTE_KEY(0x99, 105)
				REMOTE_KEY(0xc1, 106)
				REMOTE_KEY(0xc5, 158)
				REMOTE_KEY(0x88, 125)
				REMOTE_KEY(0x92, 2)
				REMOTE_KEY(0x93, 3)
				REMOTE_KEY(0xcc, 4)
				REMOTE_KEY(0x8e, 5)
				REMOTE_KEY(0x8f, 6)
				REMOTE_KEY(0xc8, 7)
				REMOTE_KEY(0x8a, 8)
				REMOTE_KEY(0x8b, 9)
				REMOTE_KEY(0xc4, 10)
				REMOTE_KEY(0x87, 11)
				REMOTE_KEY(0xd0, 522)
				REMOTE_KEY(0xcb, 14)
				REMOTE_KEY(0xda, 228)
				REMOTE_KEY(0xc9, 175)
				REMOTE_KEY(0xd9, 119)
				REMOTE_KEY(0xf0, 500)
				REMOTE_KEY(0xdd, 92)
				REMOTE_KEY(0x95, 119)
				REMOTE_KEY(0x8c, 93)>;
		};

		map_7: map_7{
			mapname = "amlogic-remote-8";
			customcode = <0x4db2>;
			release_delay = <65>;
			size  = <38>;
			keymap = <REMOTE_KEY(0xdc, 179)
				REMOTE_KEY(0x9c, 113)
				REMOTE_KEY(0xcd, 64)
				REMOTE_KEY(0x91, 65)
				REMOTE_KEY(0x83, 66)
				REMOTE_KEY(0xc3, 67)
				REMOTE_KEY(0x86, 167)
				REMOTE_KEY(0x85, 166)
				REMOTE_KEY(0x81, 114)
				REMOTE_KEY(0x80, 115)
				REMOTE_KEY(0x8d, 176)
				REMOTE_KEY(0x82, 102)
				REMOTE_KEY(0xce, 97)
				REMOTE_KEY(0xca, 103)
				REMOTE_KEY(0xd2, 108)
				REMOTE_KEY(0x99, 105)
				REMOTE_KEY(0xc1, 106)
				REMOTE_KEY(0xc5, 158)
				REMOTE_KEY(0x88, 125)
				REMOTE_KEY(0x92, 2)
				REMOTE_KEY(0x93, 3)
				REMOTE_KEY(0xcc, 4)
				REMOTE_KEY(0x8e, 5)
				REMOTE_KEY(0x8f, 6)
				REMOTE_KEY(0xc8, 7)
				REMOTE_KEY(0x8a, 8)
				REMOTE_KEY(0x8b, 9)
				REMOTE_KEY(0xc4, 10)
				REMOTE_KEY(0x87, 11)
				REMOTE_KEY(0xd0, 522)
				REMOTE_KEY(0xcb, 14)
				REMOTE_KEY(0xda, 228)
				REMOTE_KEY(0xc9, 175)
				REMOTE_KEY(0xd9, 119)
				REMOTE_KEY(0xf0, 500)
				REMOTE_KEY(0xdd, 92)
				REMOTE_KEY(0x95, 119)
				REMOTE_KEY(0x8c, 93)>;
		};

		map_8: map_8{
			mapname = "amlogic-remote-9";
			customcode = <0xc43b>;
			release_delay = <65>;
			size  = <35>;
			keymap = <REMOTE_KEY(0xdc, 179)
				REMOTE_KEY(0x9c, 113)
				REMOTE_KEY(0xcd, 64)
				REMOTE_KEY(0x91, 65)
				REMOTE_KEY(0x83, 66)
				REMOTE_KEY(0xc3, 67)
				REMOTE_KEY(0x86, 167)
				REMOTE_KEY(0x80, 115)
				REMOTE_KEY(0x8d, 176)
				REMOTE_KEY(0x82, 102)
				REMOTE_KEY(0x81, 97)
				REMOTE_KEY(0x85, 103)
				REMOTE_KEY(0x87, 108)
				REMOTE_KEY(0x99, 105)
				REMOTE_KEY(0xc1, 106)
				REMOTE_KEY(0xc5, 158)
				REMOTE_KEY(0x88, 125)
				REMOTE_KEY(0x92, 2)
				REMOTE_KEY(0x93, 3)
				REMOTE_KEY(0xcc, 4)
				REMOTE_KEY(0x8e, 5)
				REMOTE_KEY(0x8f, 6)
				REMOTE_KEY(0xc8, 7)
				REMOTE_KEY(0x8a, 8)
				REMOTE_KEY(0x8b, 9)
				REMOTE_KEY(0xc4, 10)
				REMOTE_KEY(0xd0, 522)
				REMOTE_KEY(0xcb, 14)
				REMOTE_KEY(0xda, 228)
				REMOTE_KEY(0xc9, 175)
				REMOTE_KEY(0xd9, 119)
				REMOTE_KEY(0xf0, 500)
				REMOTE_KEY(0xdd, 92)
				REMOTE_KEY(0x95, 119)
				REMOTE_KEY(0x8c, 93)>;
		};
	};

鸣谢

感谢deepseek的一个大力支持,还有amlogic写出了这么好的一个驱动代码