驱动:
common/drivers/amlogic/input/remote
remote_core.c:遥控器核心层,向input子系统注册、上报键值。
remote_cdev.c:/dev/amremote设备节点及相关的ioctl操作
remote_decoder_xmp.c:XMP红外协议解码器
remote_meson.c:红外遥控器配置相关。
remote_raw.c:使用软件方式来获取红外扫描值。
remote_regmap.c:寄存器操作相关的代码
sysfs.c:为应用程序提供sys文件节点及操作。
static int remote_probe(struct platform_device *pdev)
{
struct remote_dev *dev;
int ret;
struct remote_chip *chip;
//结构体remote_chip包含整个红外相关的信息
chip = kzalloc(sizeof(struct remote_chip), GFP_KERNEL);
//结构体remote_dev代表一个红外设备
dev = remote_allocate_device();
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);
//初始化input_dev,设置为input0,可用getevent | grep event0来获取按键事件
ir_input_device_init(dev->input_device, &pdev->dev, "aml_keypad");
//红外初始化,这里初始化硬件相关的,比如寄存器,中断、led灯还有红外协议类型。
ret = ir_hardware_init(pdev); (1)
//创建/dev/amremote设备节点
ret = ir_cdev_init(chip);
dev->rc_type = chip->protocol; //dts里面配置为REMOTE_TYPE_NEC
//向remote_core注册红外设备
ret = remote_register_device(dev); (2)
//使能红外唤醒系统功能
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);
return 0;
}
ir_hardware_init()
|–>ir_get_devtree_pdata():从dts里面获取寄存器,中断、led灯还有红外协议类型。
| |–>get_custom_tables():从dts里面获取按键键值表
|–>set_register_config(),也就是调用ir_register_default_config()
| |–>ir_contr_init()
|–>ir_interrupt() 注册中断处理函数
|–>tasklet_enable(&tasklet); 使能一个任务tasklet
从remote_reg_proto找到对应协议的寄存器信息,比如dts里面设置协议为REMOTE_TYPE_NEC:
ir_contr_init()接下来的查代码则根据reg_nec来初始化寄存器。
int remote_register_device(struct remote_dev *dev)
{
int i;
int ret;
__set_bit(EV_KEY, dev->input_device->evbit);
for (i = KEY_RESERVED; i < BTN_MISC; i++)
__set_bit(i, dev->input_device->keybit);
for (i = KEY_OK; i < BTN_TRIGGER_HAPPY; i++)
__set_bit(i, dev->input_device->keybit);
__set_bit(BTN_MOUSE, dev->input_device->keybit);
__set_bit(BTN_LEFT, dev->input_device->keybit);
__set_bit(BTN_RIGHT, dev->input_device->keybit);
__set_bit(BTN_MIDDLE, dev->input_device->keybit);
__set_bit(EV_REL, dev->input_device->evbit);
__set_bit(REL_X, dev->input_device->relbit);
__set_bit(REL_Y, dev->input_device->relbit);
__set_bit(REL_WHEEL, dev->input_device->relbit);
dev->input_device->keycodesize = sizeof(unsigned short);
dev->input_device->keycodemax = 0x1ff;
//注册input设备
ret = input_register_device(dev->input_device);
//下面用于调试,这里分配了4kb,调试时调用
dev->debug_current = 0;
dev->debug_buffer_size = 4096;
dev->debug_buffer = kzalloc(dev->debug_buffer_size, GFP_KERNEL);
if (!dev->debug_buffer) {
dev_err(dev->dev, "kzalloc debug_buffer error!\n");
ret = -ENOMEM;
}
return ret;
}
上面只是初始化过程,其中注册了中断函数ir_interrupt(),并使能中断。当操作红外遥控器时,中断被触发并调用ir_interrupt()处理(裁剪后的):
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 (MULTI_IR_SOFTWARE_DECODE(rc->protocol)) {
。。。。。。
} else {
for (cnt = 0; cnt < (ENABLE_LEGACY_IR(rc->protocol)
? 2:1); cnt++) {
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);
}
return IRQ_HANDLED;
}
tasklet是个全局变量,这是Linux中断机制里面“下半部”的一种实现方式——tasklet,其静态注册如下:
在前面ir_hardware_init()函数最后,设置了amlremote_tasklet参数为remote_chip。这里的中断处理函数ir_interrupt()主动调度tasklet,即异步调用amlremote_tasklet:
remote_keydown()函数
这里解析getkeycode(),去掉无关代码后:
ir_lookup_by_scancode()使用二分法从已经排序的键值表获得scancode对应的keycode序号。然后getkeycode()返回序号对应的keycode。
遥控器模拟鼠标功能:单击鼠标按键可切换到鼠标模式,此时在android视图上绘制鼠标的箭头. 单击上,下,左和右时,鼠标箭头可以上,下,左和右移动。
打上amlogic原厂的补丁,使能遥控功能器鼠标模式,不过代码只能适配一种遥控器。
在解析dts、获得键值表最后添加以下代码:
这里设置鼠标模式切换的按键及4个反向键和OK键,总共6个按键的扫描码。当用户操作遥控器fn_key_scancode建时,在getkeycode()进行模式切换:
此后,上下左右这4个方向键会被ir_report_rel()处理:
static int ir_report_rel(struct remote_dev *dev, u32 scancode, int status)
{
。。。。。。
/*nothing need to do in normal mode*/
//当前不是鼠标模式,则直接返回
if (!ct || (ct->ir_dev_mode != MOUSE_MODE))
return -EINVAL;
//repeat_count用于设置移动步数,长按时间越长,移动步数越大(直到最大值)
if (status == REMOTE_REPEAT) {
valid_scancode = dev->last_scancode;
repeat_count++;
if (repeat_count > ARRAY_SIZE(move_accelerate) - 1)
repeat_count = ARRAY_SIZE(move_accelerate) - 1;
} else {
valid_scancode = scancode;
dev->last_scancode = scancode;
repeat_count = 0;
}
//分别处理四个方向键,计算步数
if (valid_scancode == ct->tab.cursor_code.cursor_left_scancode) {
cursor_value = -(1 + move_accelerate[repeat_count]);
mouse_code = REL_X;
} else if (valid_scancode ==
ct->tab.cursor_code.cursor_right_scancode) {
cursor_value = 1 + move_accelerate[repeat_count];
mouse_code = REL_X;
} else if (valid_scancode ==
ct->tab.cursor_code.cursor_up_scancode) {
cursor_value = -(1 + move_accelerate[repeat_count]);
mouse_code = REL_Y;
} else if (valid_scancode ==
ct->tab.cursor_code.cursor_down_scancode) {
cursor_value = 1 + move_accelerate[repeat_count];
mouse_code = REL_Y;
} else {
return -EINVAL;
}
//发送“相对坐标”的event事件
input_event(chip->r_dev->input_device, EV_REL,
mouse_code, cursor_value);
input_sync(chip->r_dev->input_device);
return 0;
}
那OK键或Enter键如何处理呢?
BTN_LEFT是鼠标的左键。
因在get_custom_tables()只设置了鼠标键扫描值,没有设置keycode,所以使用了默认值0,即KEY_RESERVED。而在ir_do_keydown()判断keycode为KEY_RESERVED时不发送event事件。所以按遥控鼠标键仅仅是驱动里面对鼠标模式进行切换,UI界面不会有响应。
虽然上面实现了遥控器的鼠标功能,但是只能适配一种遥控器的扫描码,如果换一个遥控器怎么办?如果系统支持多个遥控器又怎么办?这些扫描码需要从dts获取:
/*return scancode*/
static __u16 ir_lookup_by_keycode(struct ir_map_tab *ir_map,
unsigned int keycode)
{
int i = 0;
int len = ir_map->map_size - 1;
for(;i <= len; i++){
if(ir_map->codemap[i].keycode == keycode)
return (__u16)(0xff - ir_map->codemap[i].scancode);
}
return (__u16)0xff;
}
static int get_custom_tables(struct device_node *node,
struct remote_chip *chip)
{
。。。。。。
memset(&ptable->tab.cursor_code, 0xff,
sizeof(struct cursor_codemap));
ptable->tab.cursor_code.fn_key_scancode = ir_lookup_by_keycode(&ptable->tab, CURSOR_FN_KEYCODE);
ptable->tab.cursor_code.cursor_left_scancode = ir_lookup_by_keycode(&ptable->tab, CURSOR_LEFT_KEYCODE);
ptable->tab.cursor_code.cursor_right_scancode = ir_lookup_by_keycode(&ptable->tab, CURSOR_RIGHT_KEYCODE);
ptable->tab.cursor_code.cursor_up_scancode = ir_lookup_by_keycode(&ptable->tab, CURSOR_UP_KEYCODE);
ptable->tab.cursor_code.cursor_down_scancode = ir_lookup_by_keycode(&ptable->tab, CURSOR_DOWN_KEYCODE);
ptable->tab.cursor_code.cursor_ok_scancode = ir_lookup_by_keycode(&ptable->tab, CURSOR_OK_KEYCODE);;
printk("cursor_code:0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
ptable->tab.cursor_code.fn_key_scancode,
ptable->tab.cursor_code.cursor_left_scancode,
ptable->tab.cursor_code.cursor_right_scancode,
ptable->tab.cursor_code.cursor_up_scancode,
ptable->tab.cursor_code.cursor_down_scancode,
ptable->tab.cursor_code.cursor_ok_scancode);
ir_scancode_sort(&ptable->tab);
}
但是,发现dts里面的键值表,KEY_F5可能有多个扫描码(可能是历史原因,不同生产批次的遥控器上某个按键可能被设置了不同的扫描码),同理其他Keycode也可能有多个扫描码。
static bool is_contain_scancode_keycode(struct ir_map_tab *ir_map,
unsigned int scancode, unsigned int keycode)
{
int index = ir_lookup_by_scancode(ir_map, scancode);
if (index < 0) {
printk("scancode %d undefined\n", scancode);
return false;
}
if( (ir_map->codemap[index].keycode == keycode) )
return true;
return false;
}
上面判断键值表里面scancode和keycode是不是一对键值。比如判断scancode对应的keycode是不是OK键:
缺点是每次按按键都要进行遍历然后判断。
Rockchip的遥控器鼠标模式主要在inputflinger实现。
鼠标移动功能需要5个按键,在framework/native/include/android/keycodes.h定义:
AKEYCODE_TV_KEYMOUSE_LEFT = 286,
AKEYCODE_TV_KEYMOUSE_RIGHT = 287,
AKEYCODE_TV_KEYMOUSE_UP = 288,
AKEYCODE_TV_KEYMOUSE_DOWN = 289,
AKEYCODE_TV_KEYMOUSE_MODE_SWITCH = 290
其中AKEYCODE_TV_KEYMOUSE_MODE_SWITCH 用于进入/退出鼠标模式,其他4个按键是方向键。
Framework/native/services/inputflinger/InputReader.cpp
KeyboardInputMapper::processKey()
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,
int32_t usageCode) {
。。。。。。
char mKeyMouseState[PROPERTY_VALUE_MAX] = "";
property_get("sys.KeyMouse.mKeyMouseState", mKeyMouseState, "off");
char mID[PROPERTY_VALUE_MAX] = "";
sprintf(mID,"%d",getDeviceId());
property_set("sys.ID.mID",mID);
if (down) {
if (keyCode == AKEYCODE_TV_KEYMOUSE_MODE_SWITCH) {
if (strcmp(mKeyMouseState, "on")==0) {
property_set("sys.KeyMouse.mKeyMouseState", "off");
} else if (strcmp(mKeyMouseState,"off")==0) {
property_set("sys.KeyMouse.mKeyMouseState","on");
}
}
。。。。。。
if (strcmp(mKeyMouseState, "on") == 0) {
if(keyCode == AKEYCODE_DPAD_LEFT) {
keyCode = AKEYCODE_TV_KEYMOUSE_LEFT;
} else if (keyCode == AKEYCODE_DPAD_RIGHT) {
keyCode = AKEYCODE_TV_KEYMOUSE_RIGHT;
} else if (keyCode == AKEYCODE_DPAD_UP) {
keyCode = AKEYCODE_TV_KEYMOUSE_UP;
} else if (keyCode == AKEYCODE_DPAD_DOWN) {
keyCode = AKEYCODE_TV_KEYMOUSE_DOWN;
}
}
如果AKEYCODE_TV_KEYMOUSE_MODE_SWITCH被按下,则根据上一次保存的鼠标模式状态进行模式切换。属性sys.KeyMouse.mKeyMouseState也用来控制Framework中其他代码的流程。
当进入鼠标模式时,对四个方向键做一些键值转换。因为按遥控器的方向键是和键盘方向键上报给InputFlinger的Event事件是一样的,所以这里将键值转换为鼠标的方向键,以便Framework对其进行特殊处理。
这些转换后的方向键,会被分发到WMS处理,KeyEvent.java也新增、定义了这些按键值:
/** Key code constant: Tv controlloer left mouse key */
public static final int KEYCODE_TV_KEYMOUSE_LEFT = 286;
/** Key code constant: Tv controlloer right mouse key*/
public static final int KEYCODE_TV_KEYMOUSE_RIGHT = 287;
/** Key code constant: Tv controlloer up mouse key*/
public static final int KEYCODE_TV_KEYMOUSE_UP = 288;
/** Key code constant: Tv controlloer down mouse key*/
public static final int KEYCODE_TV_KEYMOUSE_DOWN = 289;
/** Key code constant: Tv controlloer switch mouse key*/
public static final int KEYCODE_TV_KEYMOUSE_MODE_SWITCH = 290;
private static final int LAST_KEYCODE = KEYCODE_TV_KEYMOUSE_MODE_SWITCH;
PhoneWindowManager.java interceptKeyBeforeDispatching()对按键预处理:
mstate = SystemProperties.get("sys.KeyMouse.mKeyMouseState");
if (mstate.equals("on") && ((keyCode == KeyEvent.KEYCODE_TV_KEYMOUSE_LEFT)
|| (keyCode == KeyEvent.KEYCODE_TV_KEYMOUSE_RIGHT)
|| (keyCode == KeyEvent.KEYCODE_TV_KEYMOUSE_UP)
|| (keyCode == KeyEvent.KEYCODE_TV_KEYMOUSE_DOWN)
|| (keyCode == KeyEvent.KEYCODE_TV_KEYMOUSE_MODE_SWITCH))) {
keydown = down;
mKeyMouseHandler.sendEmptyMessage(keyCode);
//return -1;
}
发送消息给mKeyMouseHandler处理:
private int screenWidth;
private int screenHeight;
private String mstate = null;
private float mdeltax, mdeltay;
boolean keydown;
public Handler mKeyMouseHandler = new Handler() {
public void handleMessage(Message msg) {
switch(msg.what){
case KeyEvent.KEYCODE_TV_KEYMOUSE_LEFT:
mdeltax = -1.0f;
mdeltay = 0;
break;
case KeyEvent.KEYCODE_TV_KEYMOUSE_RIGHT:
mdeltax = 1.0f;
mdeltay = 0;
break;
case KeyEvent.KEYCODE_TV_KEYMOUSE_UP:
mdeltax = 0;
mdeltay = -1.0f;
break;
case KeyEvent.KEYCODE_TV_KEYMOUSE_DOWN:
mdeltax = 0;
mdeltay = 1.0f;
break;
case KeyEvent.KEYCODE_TV_KEYMOUSE_MODE_SWITCH:
mdeltax = 0;
mdeltay = 0;
break;
default:
break;
}
try {
//移动鼠标光标位置
mWindowManager.dispatchMouse(mdeltax,mdeltay,screenWidth,screenHeight);
} catch (Exception e){
e.printStackTrace();
}
if (keydown) {//如果按键没有弹起,则继续发送消息(模拟长按事件)
mKeyMouseHandler.sendEmptyMessageDelayed(msg.what,30);
}
}
};
mWindowManager.dispatchMouse()–>WindowManagerService::dispatchMouse()–>InputManagerService::dispatchMouse()–>android_server_InputManager_nativedispatchMouse()
static void android_server_InputManager_nativedispatchMouse(JNIEnv* env,
jclass clazz,jfloat x,jfloat y,jint w,jint h,jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
int mID;
float mx, my;
float screenWidth,screenHeight;
char *mgetID=new char[PROPERTY_VALUE_MAX];
const char *mkeyMouseState;
screenWidth=(float)w;
screenHeight=(float)h;
property_get("sys.ID.mID",mgetID,0);
mID=atoi(mgetID);
mPointerController=im->obtainPointerController(mID);
//start to dispatchMouse
mPointerController->setPresentation(
PointerControllerInterface::PRESENTATION_POINTER);
mPointerController->move(x,y);
mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
mPointerController->getPosition(&mx, &my);
//if((mx<=0)||((mx>=(screenWidth-10.0f))||(my<=0)||(my>=(screenHeight-10.0f)))
// x=0;y=0;
if (mx == 0) {
mkeyMouseState="left";
} else if (mx>=(screenWidth-5.0f)) {
mkeyMouseState="right";
} else if (my == 0) {
mkeyMouseState="up";
} else if (my >= (screenHeight-5.0f)) {
mkeyMouseState="down";
} else {
mkeyMouseState="Non-boundary";
}
property_set("sys.keymouselimitstate",mkeyMouseState);
}
这里调用PointerController用来保存、控制光标的位置信息。
显示光标:
static void android_server_InputManager_nativedispatchMouseByCd(JNIEnv* env,
jclass clazz,jfloat x,jfloat y,jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
int mID;
char *mgetID=new char[PROPERTY_VALUE_MAX];
property_get("sys.ID.mID",mgetID,0);
mID=atoi(mgetID);
mPointerController=im->obtainPointerController(mID);
//start to dispatchMouse
mPointerController->setPresentation(
PointerControllerInterface::PRESENTATION_POINTER);
mPointerController->setPosition(x,y);
mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
//mPointerController->fade(PointerControllerInterface::TRANSITION_IMMEDIATE);
}
如果要隐藏光标,则调用PointerController::fade()。默认情况下,在鼠标无操作几秒后光标会自动消失。
按鼠标模式键时,调用了TVWindowManager.java interceptKeyBeforeDispatching():
if(mKeyEnterMouseMode) {
try{
mWindowManager.keyExitMouseMode();
mKeyEnterMouseMode = false;
}
} else {
try {
mWindowManager.keyEnterMouseMode();
mWindowManager.keySetMouseDistance(Settings.System.getInt(mContext.getContentResolver(), Settings.System.MOUSE_ADVANCE, 30));
mWindowManager.keySetMouseBtnCode(mLeftBtn, mMidBtn, mRightBtn);
mWindowManager.keySetMouseMoveCode(mLeft, mRight, mTop, mBottom);
mKeyEnterMouseMode = true;
}
}
进入鼠标模式会调用keyEnterMouseMode等函数,而退出鼠标模式则调用keyExitMouseMode()。keyEnterMouseMode最终调用到Native层:
//support mouse mode
void InputReader::keyEnterMouseMode(){
ALOGD("Enter mouse mode!");
mKeyInMouseMode = true;
ssize_t deviceIndex = mDevices.indexOfKey(mMouseDeviceId);
if (deviceIndex < 0){
ALOGW("Discarding event for unknown deviceId %d.", mMouseDeviceId);
return;
}
InputDevice* device = mDevices.valueAt(deviceIndex);
if (device->isIgnored()){
return;
}
}
接下来如果按遥控器方向键,则在InputReader::processEventsLocked()–>convertEvent()转换为鼠标按键:
//support mouse mode
void InputReader::convertEvent(const RawEvent* rawEvents,size_t count) {
RawEvent *tmpRawEvent = mConvertEventBuffer;
for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++,tmpRawEvent++){
tmpRawEvent->deviceId = rawEvent->deviceId;
tmpRawEvent->code = rawEvent->code;
tmpRawEvent->type = rawEvent->type;
tmpRawEvent->value = rawEvent->value;
tmpRawEvent->when = rawEvent->when;
if(mKeyInMouseMode){
if(true){//tmpRawEvent->deviceId == mKeyDeviceId
if(rawEvent->type == EV_KEY) {
if(rawEvent->code == mLeft && rawEvent->value != 0) {
tmpRawEvent->deviceId = mMouseDeviceId;
tmpRawEvent->type = EV_REL;
tmpRawEvent->code = REL_X;
tmpRawEvent->value = -mDistance;
mKeySynced = false;
}else if(rawEvent->code == mRight && rawEvent->value != 0){
tmpRawEvent->deviceId = mMouseDeviceId;
tmpRawEvent->type = EV_REL;
tmpRawEvent->code = REL_X;
tmpRawEvent->value = mDistance;
mKeySynced = false;
}else if(rawEvent->code == mTop && rawEvent->value != 0){
tmpRawEvent->deviceId = mMouseDeviceId;
tmpRawEvent->type = EV_REL;
tmpRawEvent->code = REL_Y;
tmpRawEvent->value = -mDistance;
mKeySynced = false;
} else if(rawEvent->code == mBottom && rawEvent->value != 0){
tmpRawEvent->deviceId = mMouseDeviceId;
tmpRawEvent->type = EV_REL;
tmpRawEvent->code = REL_Y;
tmpRawEvent->value = mDistance;
mKeySynced = false;
} else if(rawEvent->code == mLeftBtn){
tmpRawEvent->deviceId = mMouseDeviceId;
tmpRawEvent->type = EV_KEY;
tmpRawEvent->code = BTN_LEFT;
mKeySynced = false;
} else if(rawEvent->code == mMidBtn){
tmpRawEvent->deviceId = mMouseDeviceId;
tmpRawEvent->type = EV_KEY;
tmpRawEvent->code = BTN_MIDDLE;
mKeySynced = false;
} else if(rawEvent->code == mRightBtn){
tmpRawEvent->deviceId = mMouseDeviceId;
tmpRawEvent->type = EV_KEY;
tmpRawEvent->code = BTN_RIGHT;
mKeySynced = false;
}
} else if(rawEvent->type == EV_SYN){
if(mKeySynced == false){
tmpRawEvent->deviceId = mMouseDeviceId;
mKeySynced = true;
}
}
}
}
}
}
如上重新封装一个RawEvent,其中DeviceId设置为Mouse,之后RawEvent将被CursorInputMapper处理。