android红外遥控驱动

在 Linux 内核中,IR 驱动仅支持 NEC 编码格式。

设备树文件

        pwm0: pwm@ff680000 {
                compatible = "rockchip,rk-pwm";
                reg = <0xff680000 0x10>;

                /* used by driver on remotectl'pwm */
                interrupts = <GIC_SPI 78 IRQ_TYPE_LEVEL_HIGH>;
                #pwm-cells = <2>;
                pinctrl-names = "default";
                pinctrl-0 = <&pwm0_pin>;
                clocks = <&clk_gates11 11>;
                clock-names = "pclk_pwm";
                status = "disabled";
        };



/*
 * Due to not have the software of PWM for remotectrl.
 * We can _*HACK*_ do that as the following.
 */
&pwm0 {
        compatible = "rockchip,remotectl-pwm";
        remote_pwm_id = <0>;
        handle_cpu_id = <1>;
        status = "okay";

        ir_key1{
                rockchip,usercode = <0xff00>;
                rockchip,key_table =
                        <0xeb   KEY_POWER>,
                        <0xec   KEY_MENU>,
                        <0xfe   KEY_BACK>,
                        <0xb7   KEY_HOME>,
                        <0xa3   250>,
                        <0xf4   KEY_VOLUMEUP>,
                        <0xa7   KEY_VOLUMEDOWN>,
                        <0xf8   KEY_REPLY>,
                        <0xfc   KEY_UP>,
                        <0xfd   KEY_DOWN>,
                        <0xf1   KEY_LEFT>,
                        <0xe5   KEY_RIGHT>;
        };
};

驱动文件

drivers/input/remotectl/rockchip_pwm_remotectl.c

核心结构体rk_pwm_driver,使用平台总线,

static const struct of_device_id rk_pwm_of_match[] = {
	{ .compatible =  "rockchip,remotectl-pwm"},
	{ }
};

MODULE_DEVICE_TABLE(of, rk_pwm_of_match);

static struct platform_driver rk_pwm_driver = {
	.driver = {
		.name = "remotectl-pwm",
		.of_match_table = rk_pwm_of_match,
#ifdef CONFIG_PM
		.pm = &remotectl_pm_ops,
#endif
	},
	.probe = rk_pwm_probe,
	.remove = rk_pwm_remove,
};

rk_pwm_probe -->

	struct device_node *np = pdev->dev.of_node;	//得到设备的device_node节点
	struct rkxx_remotectl_drvdata *ddata;	//定义私有数据
	ddata = devm_kzalloc(&pdev->dev, sizeof(struct rkxx_remotectl_drvdata),GFP_KERNEL);	//私有数据分配空间
	
	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);//得到reg寄存器的地址
	ddata->base = devm_ioremap_resource(&pdev->dev, r);//reg地址映射到内核空间
	clk = devm_clk_get(&pdev->dev, "pclk_pwm");//获得时钟
	platform_set_drvdata(pdev, ddata);	//把私有数据设置到平台设备里面
	num = rk_remotectl_get_irkeybd_count(pdev); //获取设备树的 rockchip,usercode = <0xff00>; 的个数,这里keybd_count=1;
	ddata->maxkeybdnum = num; //设置最大的keybdnum=1;
	remotectl_button = kmalloc(num*sizeof(struct rkxx_remotectl_button),GFP_KERNEL);//给struct rkxx_remotectl_button分配空间(根据keybdnum数量)
	
	//以下将创建、设置、注册一个input_dev结构体(输入子系统的框架)
	input = input_allocate_device();		//内核API,帮助我们创建input_dev结构体,并做一些初始化。
	input->name = pdev->name;							//设置名字,android将使用这个名字找到对应的keylayout文件
	input->phys = "gpio-keys/remotectl";
	input->dev.parent = &pdev->dev;
	input->id.bustype = BUS_HOST;
	input->id.vendor = 0x0001;
	input->id.product = 0x0001;
	input->id.version = 0x0100;
	ddata->input = input;
	ddata->input = input;	
	//wake_lock一般在关闭lcd、tp但系统仍然需要正常运行的情况下使用,比如听歌、传输很大的文件等
	wake_lock_init(&ddata->remotectl_wake_lock, WAKE_LOCK_SUSPEND, "rk29_pwm_remote");
#if defined(CONFIG_RK_IR_NO_DEEP_SLEEP)
  wake_lock(&ddata->remotectl_wake_lock);
#endif

	ret = clk_prepare_enable(clk);//使能时钟
	irq = platform_get_irq(pdev, 0);//获取irq编号
	ddata->irq = irq;
	ddata->wakeup = 1;

	of_property_read_u32(np, "remote_pwm_id", &pwm_id);	//pwm_id	
	ddata->remote_pwm_id = pwm_id; //记录下用哪一个pwm,中断函数中会用到,这里使用第0个
	
	of_property_read_u32(np, "handle_cpu_id", &cpu_id); //获取cpu_id
	ddata->handle_cpu_id = cpu_id;
	
	rk_remotectl_parse_ir_keys(pdev); -->//解析设备树的键值和扫描码,并保存在remotectl_button[0].key_table 这个结构体数组里面
			of_property_read_u32(child_node, "rockchip,usercode",&remotectl_button[boardnum].usercode) //rockchip,usercode = 0xff00
			of_get_property(child_node, "rockchip,key_table", &len); //len保存着rockchip,key_table属性值的长度,字节为单位
			len /= sizeof(u32);
			DBG("len=0x%x\n",len);		//一个按键用2个32位的数表示, <0xeb键值   KEY_POWER按键码>, 
			remotectl_button[boardnum].nbuttons = len/2; //获取按键个数。
	
			/*		
			struct rkxx_remote_key_table {
				int scancode;
				int keycode;
			};
			*/
			of_property_read_u32_array(child_node, "rockchip,key_table",(u32 *)remotectl_button[boardnum].key_table, len)//key_table包含键值和扫描码 -->
	
					const __be32 *val = of_find_property_value_of_size(np, propname,(sz * sizeof(*out_values)));
					*out_values++ = be32_to_cpup(val++);//大字节序转为小端模式,这里的out_values就是代表key_table里的键值和扫描码
				
			//之后打印key_table里面的键值对。
			for (loop=0; loop<(len/2); loop++) {
				DBG("board[%d].scanCode[%d]=0x%x\n", boardnum, loop,
						remotectl_button[boardnum].key_table[loop].scancode);
				DBG("board[%d].keyCode[%d]=%d\n", boardnum, loop,
						remotectl_button[boardnum].key_table[loop].keycode);
			}		
	
			//tasklet_schedule(),tasklet_init(),
			//tasklet运行在终端上下文,不可延时、睡眠。 如果中断出发后处理事情的时间比较短可以使用此方法。 如果时间比较长,建议使用线程化中断或者work_queue。
			tasklet_init(&ddata->remote_tasklet, rk_pwm_remotectl_do_something,(unsigned long)ddata);	//rk_pwm_remotectl_do_something是中断处理函数
			
			//对于每个按键设置它能产生按键类事件
			for (j = 0; j < num; j++) {
				DBG("remotectl probe j = 0x%x\n", j);
				for (i = 0; i < remotectl_button[j].nbuttons; i++) {
					unsigned int type = EV_KEY;
		
					input_set_capability(input, type, remotectl_button[j].
							     key_table[i].keycode);
				}
			}
			
			ret = input_register_device(input);//向内核注册一个input_dev结构体
			input_set_capability(input, EV_KEY, KEY_WAKEUP);//补充设置nput_dev结构体
			device_init_wakeup(&pdev->dev, 1); //电源管理
			/*
			这些都是电源管理部分的核心数据结构,can_wakeup为1时 表明一个设备可以被唤醒,设备驱动为了支持linux中的电源管理,有责任调用device_init_wakeup()来初始化can_wakeup。
			而should_wakeup则是在设备的 电源状态发生变化时 被device_may_wakeup()用来测试,测试它该不该变化。
			can_wakeup,标识本设备是否具有唤醒能力。只有具备唤醒能力的设备,才会在sysfs中有一个power目录,用于提供所有的wakeup信息。
			*/
			
			
			enable_irq_wake(irq);
			setup_timer(&ddata->timer, rk_pwm_remotectl_timer,(unsigned long)ddata);
					
			//下面是硬件相关的操作了
			cpumask_clear(&cpumask);
			cpumask_set_cpu(cpu_id, &cpumask);
			irq_set_affinity(irq, &cpumask);
			ret = devm_request_irq(&pdev->dev, irq, rockchip_pwm_irq,IRQF_NO_SUSPEND, "rk_pwm_irq", ddata);//rockchip_pwm_irq中断函数

			rk_pwm_remotectl_hw_init(ddata);	//硬件IR寄存器相关初始化
			led_trigger_register_simple("ir-power-click", &ledtrig_ir_click);	

中断函数分析:

rockchip_pwm_irq-->
	switch (ddata->remote_pwm_id) {
	case 0: {
		val = readl_relaxed(ddata->base + PWM0_REG_INTSTS);		//读取寄存器的值
		if (val & PWM_CH0_INT) {
			if ((val & PWM_CH0_POL) == 0) {
				val = readl_relaxed(ddata->base + PWM_REG_HPR);
				ddata->period = val;
				tasklet_hi_schedule(&ddata->remote_tasklet);		//调度工作队列
				DBG("hpr=0x%x\n", val);
			} else {
				val = readl_relaxed(ddata->base + PWM_REG_LPR);
				DBG("lpr=0x%x\n", val);
			}
			writel_relaxed(PWM_CH0_INT, ddata->base + PWM0_REG_INTSTS);
#if ! defined(CONFIG_RK_IR_NO_DEEP_SLEEP)
			if (ddata->state == RMC_PRELOAD)
				wake_lock_timeout(&ddata->remotectl_wake_lock, HZ);
#endif
			return IRQ_HANDLED;
		}
	}
	break;
	case 1:	{...}
	break;
	case 2:	{...}
	break;	
	case 3:	{...}
	break;		




rk_pwm_remotectl_do_something -->

	struct rkxx_remotectl_drvdata *ddata;

	ddata = (struct rkxx_remotectl_drvdata *)data;
	switch (ddata->state) {
	case RMC_IDLE: {
		;
		break;
	}
	case RMC_PRELOAD: {
	...
	case RMC_USERCODE: {	
		if ((RK_PWM_TIME_BIT1_MIN < ddata->period) &&
		    (ddata->period < RK_PWM_TIME_BIT1_MAX))
			ddata->scandata |= (0x01 << ddata->count);
		ddata->count++;
		if (ddata->count == 0x10) {
			DBG_CODE("USERCODE=0x%x\n", ddata->scandata);
			
			//在remotectl_button[i]的数组里面查找有没有对应的usercode
			if (remotectl_keybd_num_lookup(ddata)) {	
				ddata->state = RMC_GETDATA;
				ddata->scandata = 0;
				ddata->count = 0;
			} else {
				if (rk_remote_print_code){
					ddata->state = RMC_GETDATA;
					ddata->scandata = 0;
					ddata->count = 0;
				} else
					ddata->state = RMC_PRELOAD;
			}
		}
	}
	break;
	case RMC_GETDATA: {
                if(!get_state_remotectl() && (ddata->keycode != KEY_POWER))
                {
                        ledtrig_ir_activity();
                }
		if ((RK_PWM_TIME_BIT1_MIN < ddata->period) &&
		    (ddata->period < RK_PWM_TIME_BIT1_MAX))
			ddata->scandata |= (0x01<<ddata->count);
		ddata->count++;
		if (ddata->count < 0x10)
			return;
		DBG_CODE("RMC_GETDATA=%x\n", (ddata->scandata>>8));
		if ((ddata->scandata&0x0ff) ==
		    ((~ddata->scandata >> 8) & 0x0ff)) {
			if (remotectl_keycode_lookup(ddata)) {
				ddata->press = 1;
				input_event(ddata->input, EV_KEY,
					    ddata->keycode, 1);						//上报事件
				input_sync(ddata->input);
				ddata->state = RMC_SEQUENCE;
			} else {
				ddata->state = RMC_PRELOAD;
			}
		} else {
			ddata->state = RMC_PRELOAD;
		}
	}
	break;
	break;
	case RMC_SEQUENCE:{
	...
	break;
	default:
	break;
	}



配置内核
 Symbol: ROCKCHIP_REMOTECTL_PWM [=y]                                                                                                                                               │

│ Type : boolean │
│ Prompt: rockchip remoctrl pwm capture │
│ Location: │
│ -> Device Drivers │
│ -> Input device support │
│ -> Generic input layer (needed for keyboard, mouse, …) (INPUT [=y]) │
│ (1) -> rockchip remotectl (ROCKCHIP_REMOTECTL [=y]) │
│ Defined at drivers/input/remotectl/Kconfig:14 │
│ Depends on: !UML && INPUT [=y] && ROCKCHIP_REMOTECTL [=y]

代码调试
#define DBG_CODE(args…)
do {
if (rk_remote_print_code) {
pr_info(args);
}
} while (0)

当rk_remote_print_code为非0是执行打印
module_param_named(code_print, rk_remote_print_code, int, 0644);

通过指令打开红外接收的打印功能,然后按遥控器按键,就可以在内核打印中看到用户码和键值。

$ adb shell
root@rk3288:/ # cd sys/module/rockchip_pwm_remotectl/parameters
cd sys/module/rockchip_pwm_remotectl/parameters
root@rk3288:/sys/module/rockchip_pwm_remotectl/parameters # ls
ls
code_print
dbg_level
root@rk3288:/sys/module/rockchip_pwm_remotectl/parameters # cat code_print
cat code_print
0
root@rk3288:/sys/module/rockchip_pwm_remotectl/parameters # echo 1 > code_print
echo 1 > code_print

对着红外接收管按下按钮出现一些打印信息
[ 854.592600] RMC_GETDATA=e9
[ 865.225154] USERCODE=0xff00
[ 865.251447] RMC_GETDATA=f3
[ 867.237842] USERCODE=0xff00
[ 867.264304] RMC_GETDATA=e7
[ 867.811200] USERCODE=0xff00
[ 867.837489] RMC_GETDATA=a1
[ 868.804076] USERCODE=0xff00
[ 868.830518] RMC_GETDATA=f7
[ 869.389795] USERCODE=0xff00
[ 869.416089] RMC_GETDATA=e3

修改设备数支持我的红外遥控

&pwm0 {
        compatible = "rockchip,remotectl-pwm";
        remote_pwm_id = <0>;
        handle_cpu_id = <1>;
        status = "okay";

        ir_key1{
                rockchip,usercode = <0xff00>;
                rockchip,key_table =
                        <0xba   KEY_POWER>,
                        <0xb9   KEY_MODE>,
                        <0xb8   KEY_MUTE>,
                        <0xbb   KEY_PAUSE>,
                        <0xbc   KEY_NEXT>,
                        <0xbf   KEY_LAST>,
                        <0xea   KEY_VOLUMEDOWN>,
                        <0xf6   KEY_VOLUMEUP>,
                        <0xad   KEY_DOWN>,
                        <0xf7   KEY_LEFT>,
                        <0xa5   KEY_RIGHT>,
                        <0xe7   KEY_UP>, 
                        <0xe3   KEY_ENTER>,     
                        <0xb5   KEY_NUMERIC_9>,    
                        <0xf3	KEY_BACK>;                                                                                   
        };
};

修改kl文件

key 28    ENTER
#key 116   POWER
key 116   POWER
key 158   BACK
key 139   MENU
key 217   SEARCH
key 232   DPAD_CENTER
key 108   DPAD_DOWN
key 103   DPAD_UP
key 102   HOME
key 105   DPAD_LEFT
key 106   DPAD_RIGHT
key 115   VOLUME_UP
key 114   VOLUME_DOWN
key 143   NOTIFICATION
key 113   VOLUME_MUTE
key 250   FIREFLY_RECENT
key 388   TV_KEYMOUSE_MODE_SWITCH
key 0x209  9
#key 400   TV_MEDIA_MULT_BACKWARD
#key 401   TV_MEDIA_MULT_FORWARD
#key 402   TV_MEDIA_PLAY_PAUSE
#key 64    TV_MEDIA_PLAY
#key 65    TV_MEDIA_PAUSE
#key 66    TV_MEDIA_STOP
#key 67    TV_MEDIA_REWIND
#key 68    TV_MEDIA_FAST_FORWARD
#key 87    TV_MEDIA_PREVIOUS
#key 88    TV_MEDIA_NEXT

idc文件:input device configure
它包含设备具体的配置属性,这些属性影响输入设备的行为。对于touch screen设备,总是需要一个idc文件来定义其行为。
Android基于输入设备驱动汇报的事件类型和属性来检测和配置大部分输入设备的能力。然而有些分类是模棱两可的,如:多点触摸屏(multi-touch touch screen)和touch pad都支持EV_ABS事件类型和ABS_MT_POSITION_X和ABS_MT_POSTION_Y事件,然而这两类设备的使用是不同的,且不总是能自动判断。所以,需要另外的信息来指示设备上报的pressrue和size信息的真正含义。因为,触摸设备,特别是内嵌的touch screen,经常需要idc文件。
keylayout: 只是用来表示驱动上报的scancode对应哪一个android按键(AKEYCODE_x) 它对应哪一个字符,由kcm文件决定
kcm: 用来表示android按键(AKEYCODE_x)对应哪一个字符,表示同时按下其他按键后,对应哪个字符

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值