在 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)对应哪一个字符,表示同时按下其他按键后,对应哪个字符