RK3066 遥控器调试流程

RK3066 遥控器流程
1. 先看看配置的驱动程序
	#vi kernel/.config
	CONFIG_RECKCHIP_REMOTECTL=y
	CONFIG_RK_REMOTECTL=y

2. 查找在哪个配置文件中配置了上述选项,便可以知道驱动程序的源程序了。
	#vi kernel/drivers/input/remotectl/Kconfig
	menuconfig ROCKCHIP_REMOTECTL
		bool "rkxx remotectl"
		default y
		help
			...
	if ROCKCHIP_REMOTECTL
	config RK_REMOTECTL
		bool "rkxx remotectl"
		default y
	...
	endif
#vi kernel/drivers/input/remotectl/Makefile
	obj-$(CONFIG_RK_REMOTECTL) += rkxx_remotectl.o
可以知道,如果配置了 RK_REMOTECTL为y,则把rkxx_remotectl.o放入内核,这个rkxx_remotectl.o会对应一个rkxx_remotectl.c文件
3. 分析源程序
#vi kernel/drivers/input/remotectl/rkxx_remotectl.c

	static int __devinit remotectl_probe(struc platform_device *pdev)
	{
		struct RKxx_remotectl_platform_data *pdata;	//存放遥控器平台数据的结构体
		pdata = pdev->dev.platform_data;
		struc rkxx_remotectl_drvdata *ddata;	//存放驱动数据的结构体,比如当前按键状态,按键码,当前时间等等信息
		...
		ddata = kzalloc(sizeof(struct rkxx_remotectl_drvdata),GFP_KERNEL);	//对任何结构体使用之前一定要分配内存
		memset(ddata, 0, sizeof(struct rkxx_remotectl_drvdata));	//清空数据,以免有“脏”数据
		
		ddata->state = RMC_PRELAOD;	//设定此时的状态,总共有typedef enum _RMC_STATE{RMC_IDLE,RMC_PRELOAD,RMC_USERCODE,RMC_GETDATA,RMC_SEQUENCE}eRMC_STATE这么几个状态
		input = input_allocate_device();	//分配一个输入设备。对应input_free_device,释放一个输入设备
		
		platform_set_drvdata(pdev,ddata);	//通过指针指向,让平台设备和这个驱动连接起来
		
		//为输入设备设置一些信息
		input->name = pdev->name;
		input->phys = "gpio-keys/input0";
		input->dev.parent = &pdev->dev;
		input->id.bustype = BUS_HOST;
		input->id.vendor = 0x0001;
		input->id.product = 0x0001;
		input->id.version = 0x0100;
		
		if(pdata->rep)	//假如驱动支持自动重复功能
			__set_bit(EV_REP, input->evbit);	//使用Linux子系统的自动重复功能(Enable auto repeat feature of Linux input subsystem)
		
		ddata->input = input;	//驱动的input指向这个input
		wake_lock_init(&ddata->remotectl_wake_lock, WAKE_LOCK_SUSPEND,"rk29_remote");	//初始化这个锁主要目的是遥控器有开关机键,需要再点击的时候唤醒或者休眠设备,此功能暂时由上层实现。
		...
		error = gpio_request(pdata->gpio, "remotectl");	//使用gpio方式控制的按键驱动,必须先申请GPIO(These"optonal" allocation calls help prevent drivers from stomping on each other, and help provide better diagnostics debugfs. They're called even less than the "set direction" call)
		...error处理,goto fail1;
		error = gpio_direction_input(pdata->gpio);	//(Drivers Must set GPIO directoin before making get/set calls. In some cases this is done in early boot, befor IRQs are enabled.)
		...error处理,goto fail1;
		irq = gpio_to_irq(pdata->gpio);	//(return the IRQ corresponding to a GPIO,@gpio whose IRQ will be return{already requested})
		...error处理,goto fail1;
		error = request_irq(irq, remotectl_isr,IRQF_TRIGGER_FALLING,"remotectl",ddata);	//请求中断
		...error处理,goto fail1;
		
		setup_timer(&ddata->timer, remotectl_timer, (unsigned long)ddata;	//设置定时器,回调函数为remotectl_timer
		tasklet_init(&ddata->remote_tasklet, remotectl_do_something, (unsigned long)ddata);	//初始化一个tasklet,回调函数为remotectl_do_something
		/*
		底层驱动可以通过struct input_event上报事件信息,上层应用也可以通过此结构体来获取事件信息
		struct input_event {
		         structtimeval time;   //事件产生时间
		         __u16type;            //事件类型
		         __u16code;            //事件代码
		         __s32value;           //事件值
		};
		
		事件类型,定义在linux/input.h中
		#define EV_SYN          0x00    //表示设备支持所有的事件
		#define EV_KEY          0x01    //键盘或者按键,表示一个键码
		#define EV_REL          0x02    //鼠标设备,表示一个相对的光标位置结果
		#define EV_ABS          0x03    //手写板产生的值,其是一个绝对整数值
		#define EV_MSC          0x04    //其他类型
		#define EV_LED          0x11    //LED灯设备
		#define EV_SND          0x12    //蜂鸣器,输入声音
		#define EV_REP          0x14    //允许重复按键类型
		#define EV_PWR          0x16    //电源管理事件 
		
		事件代码,不同事件类型包含了多种事件代码
		EV_KEY                                KEY_1,KEY_2,KEY_3…
		EV_REL                                REL_X,REL_Y, REL_Z…  
		EV_ABS                                ABS_X,ABS_Y, ABS_Z…
		事件值,由硬件输入设备产生的,比如按键设备驱动会去获取GPIO引脚的状态(假设低电平有效),按下时是0,弹起时是1
		*/
		for(j = 0; j < sizeof(remotectl_button)/sizeof(struct rkxx_remotectl_button); 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);	//设置输入设备的功能:按键功能,支持keyCode按键
				//input_set_capability相当于:
				/*
				//设置支持的事件类型(支持按键事件)
          set_bit(EV_KEY, input->evbit);
				//设置支持的事件代码(支持按键1为set_bit(KEY_1, input->keybit);)	
          set_bit(remotectl_button[j].key_table[i].keyCode, input->keybit);
				*/
			}
		}
		
		error = input_register_device(input);	//注册输入设备,用于将input注册进核心层中
		//error处理
		input_set_capability(input, EV_KEY, KEY_WAKEUP);	//设置输入设备的功能:唤醒功能
		device_init_wakeup(&pdev->dev, 1);
		
		return 0;
		fail*:
			//错误处理
	}
	
	static struct platform_driver remotectl_device_driver = {
		.probe = remotectl_probe,	//当系统设备匹配上驱动函数,则会回调这个函数完成具体设备的初始化
		.remove = __devexit_p(remotectl_remove),
		.driver = {
			.name = "rkxx-remotectl",
			.owner = THIS_MODULE,
	#ifdef CONFIG_PM
		.pm = &remotectl_pm_ops,	//电源管理的回调函数注册
	#endif
		}
	}
	
	static int remotectl_init(void)	//初始化函数
	{
		return platform_driver_register(&remotectl_device_driver);
	}
	
	static int remotectl_exit(void)	//退出函数
	{
		return platform_driver_unregister(&remotectl_device_driver);
	}
	
	module_init(remotectl_init);
	module_exit(remotectl_exit);
	MODULE_DESCRIPTION("Keyboard driver for CUP GPIOs");	//知道是个GPIO型的驱动
	
	以上为准备数据,接下来我们来看看设备是如何上报按键的,在这之前我说一句,上报按键你第一放映可能是找input_report_key,不过就算你找不到也没关系,因为:
	报告按键值
	void input_report_key(struct input_dev *dev, unsigned int code, int value)
	
	报告相对坐标
	void input_report_rel(struct input_dev *dev, unsigned int code, int value)
	
	报告绝对坐标
	void input_report_abs(struct input_dev *dev, unsigned int code, int value)
	
	用于事件同步 ,防止数据混乱
	void input_sync(struct input_dev *dev)
	
	以上都是对Input_event的封装
	void input_event(struct input_dev *dev,unsigned int type, unsigned int code, intvalue)
	
	input_report_key原型:
	static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
	{
		input_event(dev,EV_KEY, code, !!value);	//!!value是防止value不是0或1,而产生的问题
	}
	
	遥控器设备和按键设备一样,是通过用户按按键,从而产生硬件中断,系统通过注册这个中断来在中断被触发的时候回调中断函数来实现上报键值的。
	
	static irqreturn_t remotectl_isr(int irq, void *dev_id)
	{
		...
		ddata->period = ddata->cur_time - ddata->pre_time;	//获得按键时间
		//调度&ddata->remote_tasklet,去执行tasklet指定的回调函数remotectl_do_something
		tasklet_hi_schedule(&ddata->remote_tasklet);	//tasklet_schedule()函数和tasklet_hi_schedule()函数分别用来在当前CPU上触发软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ,并把指定的tasklet加入当前CPU所对应的tasklet队列中去等待执行。tasklet_hi_schedule()具有更高的优先级
		if((ddata->state == RMC_PRELOAD) || (ddata->state == RMC_SEQUENCE))
			mod_timer(&ddata->timer, jiffies + msecs_to_jiffies(130));	//在130毫秒后执行定时器指定的回调函数
	}
	
	static void remotectl_do_something(unsigned long data)
	{
		/*接受遥控器数据的流程为
		RMC_PRELOAD -> RMC_USERCODE -> RMC_GETDATA ( -> RMC_SEQUENCE 貌似这个可以实现数据排队,回头验证过在补充这个内容)
		*/
		struct rkxx_remotectl_drvdata *ddata = (struct rkxx_remotectl_drvdata *)data;
		switch(ddata->state)
		{
			case RMC_IDLE:
				break;
			case RMC_PRELOAD:
				//#define TIME_PRE_MIN 13000
				//#define TIME_PRE_MAX 14000
				if((TIME_PRE_MIN < ddata->period) && (ddata->period < TIME_PRE_MAX)){	//不知道判断这个period的原因,目的好像是符合要求后,将状态设置为可以接受用户码状态RMC_USERCODE
					ddata->scanData = 0;
					ddata->count = 0;
					ddata->state = RMC_USERCODE;
				}else{
					ddata-> state = RMC_PRELOAD;
				}
				ddata->pre_time = ddata->cur_time;
				break;
			case RMC_USERCODE:
				ddata->scanData <<= 1;	//处理scanData,根据遥控器上报的数据来处理的,要看遥控器的spec或者直接和遥控器厂商沟通,但是更常见的是他们有驱动参考代码
				ddata->count++;	//因为usercode是16bit的,所以用count计数,要等到0x10才算接收到一个完整的usercode
				if((TIME_BIT1_MIN < ddata->preiod) && (ddata->preiod < TIME_BIT1_MAN)){
					ddata->scanData |= 0x01;	//根据spec在处理scanData的值
				}
				if(ddata->count == 0x10){	16bit usercode
					if(remotectl_keybdNum_lookup(ddata){	//确定使用的usercode和其对应的码值
						ddata->state = RMC_GETDATA;
						ddata->scanData = 0;
						ddata->count = 0;
					}else{
						ddata->state = 	RMC_PRELOAD;
					}
				}
				break;
			case RMC_GETDATA:
				ddata->count++;
				ddata->scanData <<=1;
				if((TIME_BIT1_MIN < ddata->period) && (ddata->period < TIME_BIT1_MAX)){
					ddata->scanData |= 0x01;
				}
				if(ddata->count == 0x10){
					if((ddata->scanData&0x0ff) == ((!ddata->scanData >> 8)&0x0ff)){	//&0x0ff是取低8位
						if(remotectl_keycode_lookup(ddata)){
							ddata->press = 1;
							if(get_suspend_state() == 0){	//如果系统没有休眠,则发送按下的键值
								input_event(ddata->input, EV_KEY, ddata->keycode, 1);
								input_sync(ddata->input);
							}else if((get_suspend_state()) && (ddata->keycode==KEY_POWER)){	//如果系统在休眠模式,并且按下的键值是KEY_POWER,则发送KEY_WAKEUP来唤醒系统(当然要处理这个按键来实现唤醒系统)
								input_event(ddata->input, EV_KEY, KEY_WAKEUP, 1);
								input_sync(ddata->input);
							}
							ddata-> state = RMC_SEQUENCE;
						}else{
							ddata->state = RMC_PRELOAD;
						}
					}
				}
				break;
			case RMC_SEQUENCE:
				if((TIME_RPT_MIN < ddata->period) && (ddata->period < TIME_RPT_MAX)){
					;
				}else if((TIME_SEQ_MIN < ddata->period) && (ddata->period < TIME_SEQ_MAX)){
					if(ddata->press == 1){
						ddata->press = 3;
					}else if(ddata->press & 0x2){
						ddata->press = 2;
					}
				}
			default:
			
		}
	}
	
	看到这里,就知道按键是如何上报的吧,但是细心的观众会发现,怎么只有个按键按下,没有松开呢
	就是在收到中断后,在触发了读按键之后的130毫秒之后会启动定时器的回调函数
	static void remotectl_timer(unsigned long data)
	{
		struct rkxx_remotectl_drvdata *ddata = (struct rkxx_remotectl_drvdata*)__data;
		if(ddata->press != ddata->pre_press){
			ddata->pre_press = ddata->press = 0;
			if(get_suspend_state() == 0){
				input_event(ddata->input, EV_KEY, ddata->keycode, 0);
				input_sync(ddata->input);
			}else if((get_suspend_state()) && (ddata->keycode == KEY_POWER)){
				input_event(ddata->input, EV_KEY, KEY_WAKEUP, 0);
				input_sync(ddata->input);
			}
		}
	}

注意:看log可知,遥控器每次上报一个bit就会产生一个中断,至于报值顺序及规则可参考NEC协议原理。(推荐一个:http://download.csdn.net/detail/lijinwei_123/5698249)

在调试中碰到的问题:
1. 没有操作遥控器,但是驱动显示有中断产生 
按给出的usercode码和键值对应关系,增加其映射和相关log后,编译-烧录,发现不能正常响应按键,看log知道没有进入USERCODE,观察了一下,竟然没有操作遥控器,中断也会上报。这个基本上可以确定是硬件的原因。更换板子后正常。

2. 代码里读出的数据和遥控器厂商提供的值正好是反序
遥控器厂商说这个是正常的,因为我们提供的码值他们的软件会取个反序发出的,如果想修改的话,我们直接提供一个反序的码值就可以了,那就根据遥控器报值值在驱动里对应就可以了

你可能感兴趣的:(Andriod,Linux)