本文共 19806 字,大约阅读时间需要 66 分钟。
驱动:
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文件节点及操作。驱动代码的入口在remote_meson.c:
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(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(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 modevoid 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 modevoid 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处理。
转载地址:http://qmoxi.baihongyu.com/