一:基础知识
硬件原理
NORMAL_TYPE
Row0--Row7
KCOL0--KCOL7
EXTEND_TYPE
Row0--Row3
KCOL0 KCOL0_R
KCOL1 KCOL1_R
KCOL2-KCOL2_R
读建方式
1.kernel 按键配置
Set to Y to enable MTK keypad driver.
• CONFIG_ONEKEY_REBOOT_NORMAL_MODE
Set to Y to set long press reboot by power key on normal mode.
• CONFIG_ONEKEY_REBOOT_OTHER_MODE
Set to Y to set long press reboot by power key on other boot mode.
• CONFIG_ KPD_PMIC_LPRST_TD
Long press reset time
0: 8 second, 1: 11 second, 2: 14 second, 3: 5 second
CONFIG_KEYBOARD_MTK=y
CONFIG_TWOKEY_REBOOT_NORMAL_MODE=y
CONFIG_TWOKEY_REBOOT_OTHER_MODE=y
CONFIG_KPD_PWRKEY_USE_PMIC=y
2.按键采用中断 tasklet 轮询方式读键值 tasklet 在软件中断上下文中运行,所以 tasklet 代码必须是原子的
static DECLARE_TASKLET(kpd_keymap_tasklet, kpd_keymap_handler, 0); //初始化 tasklet
static void kpd_keymap_handler(unsigned long data)
{
int i, j;
bool pressed;
u16 new_state[KPD_NUM_MEMS], change, mask;
u16 hw_keycode, linux_keycode;
kpd_get_keymap_state(new_state);
wake_lock_timeout(&kpd_suspend_lock, HZ / 2);
for (i = 0; i < KPD_NUM_MEMS; i++) { //轮询
change = new_state[i] ^ kpd_keymap_state[i];
if (!change)
continue;
for (j = 0; j < 16; j++) {
mask = 1U << j;
if (!(change & mask))
continue;
hw_keycode = (i << 4) + j;
/* bit is 1: not pressed, 0: pressed */
pressed = !(new_state[i] & mask);
if (kpd_show_hw_keycode)
kpd_print("(%s) HW keycode = %u\n", pressed ? "pressed" : "released", hw_keycode);
BUG_ON(hw_keycode >= KPD_NUM_KEYS);
linux_keycode = kpd_keymap[hw_keycode]; //可以打印log ,查看
if (unlikely(linux_keycode == 0)) {
kpd_print("Linux keycode = 0\n");
continue;
}
kpd_aee_handler(linux_keycode, pressed);
input_report_key(kpd_input_dev, linux_keycode, pressed);
input_sync(kpd_input_dev);
kpd_print("report Linux keycode = %u\n", linux_keycode);
}
}
memcpy(kpd_keymap_state, new_state, sizeof(new_state));
kpd_print("save new keymap state\n");
enable_irq(kp_irqnr); //开中断
}
static irqreturn_t kpd_irq_handler(int irq, void *dev_id)
{
disable_irq_nosync(kp_irqnr); //在中断处理函数中使用,不会阻塞;用于屏蔽相应中断;
tasklet_schedule(&kpd_keymap_tasklet); /能使系统在适当的时候进行调度 kpd_keymap_handler
return IRQ_HANDLED; //中断是由本设备引起的
}
static int kpd_pdrv_probe(struct platform_device *pdev)
{
kp_irqnr = irq_of_parse_and_map(pdev->dev.of_node, 0);
kpd_set_debounce(kpd_dts_data.kpd_key_debounce);
r = request_irq(kp_irqnr, kpd_irq_handler, IRQF_TRIGGER_NONE, KPD_NAME, NULL);
mt_eint_register(); //开中断
}
3.按键从kernel到Android的过程
./kernel-3.18/drivers/input/keyboard/mediatek/kpd.c 驱动
./kernel-3.18/include/uapi/linux/input.h kernel 键值
#define KEY_VOLUMEUP 115
./device/teksun/hys6737m_35_m0/mtk-kpd.kl linux和android key映射
key 115 VOLUME_UP
./frameworks/base/data/keyboards/Generic.kl
./frameworks/base/data/keyboards/qwerty.kl
进入system/usr/keylyout/可以看到所用的KL
frameworks/native/include/android/keycodes.h
AKEYCODE_VOLUME_UP = 24,
frameworks/base/core/java/android/view/KeyEvent.java Java 键盘事件
public static final int KEYCODE_VOLUME_UP = 24;
frameworks/base/core/res/res/values/attrs.xml
./frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
final boolean keyguardOn = keyguardOn();
final int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP){
}
}
二:按键配置
key主要指:
POWER //此键固定。不需要配置
VOLUMEUP VOLUMEDOWN
查看原理图:
KROW0
KCOL0 VOLUMEUP
KCOL1 VOLUMEDOWN
在dws中配置:
Column0 Column1
Row0 VOLUMEUP VOLUMEDOWN
在dws中配置gpio(L - IN ; R - OUT)
EintMode|Def.Mode M0|M1|M2|M3|M4|M5|M6|M7|InPull En|InPull SelHigh|Def.Dir|In|Out|OutHigh|VarName1
GPIO81 0:KPROW0 1 1 OUT 0 1 0 GPIO_KPD_KROW0_PIN
GPIO84 0:KPCOL0 1 1 1 1 IN 1 1 0 GPIO_KPD_KCOL0_PIN
GPIO85 0:KPCOL1 1 1 1 1 IN 1 1 0 GPIO_KPD_KCOL1_PIN
dws配置EINT : NC 即可
三:调试方法
1.1 kernel查看驱动上报事件 - 驱动打印:
cat /proc/kmsg | grep "key"
应用层事件 -
logcat | grep KeyCode
logcat | grep KeyEvent
1.2 kernel查看event上报事件 - kernel打印:
getevent -i -- 查看所有输入子设备的信息
getevent –t -l /dev/input/event0
1.3 查看中断计数 (按下+1, 松开+1)
cat /proc/interrupts | grep "mtk-kpd"
1.4 查看dws配置out目录生成的文件是否对
./out/target/product/hys6737m_35_m0/obj/BOOTLOADER_OBJ/build-hys6737m_35_m0/inc/cust_kpd.h
./out/target/product/hys6737m_35_m0/obj/PRELOADER_OBJ/inc/cust_kpd.h
修改dws中gpio口发现只 有lk和 pl 在out目录有生成
#define KPD_INIT_KEYMAP() \
{ \
[1] = KEY_VOLUMEUP, \
[2] = KEY_VOLUMEDOWN, \
}
四:GPIO 框架外的调试:
中断方式
1.1 dts 配置
/ { mc_key: mc_key@0 {
compatible = "mediatek,mc_key"; //定义设备
};
};
/*mc-key*/
#define __MC_KEY_EINT_PIN__ PINMUX_GPIO6__FUNC_GPIO6
#define __MC_KEY_EINT_EN_PIN_NUM__ 6
&mc_key{
interrupt-parent = <&eintc>;
interrupts = <__MC_KEY_EINT_EN_PIN_NUM__ IRQ_TYPE_EDGE_FALLING>;
debounce = <__MC_KEY_EINT_EN_PIN_NUM__ 100>;
status = "okay";
};
&pio {
mc_key_default:mc_key_default{
};
mc_key_as_int: mc_key_as_int@0{
pins_cmd_dat {
pins = <__MC_KEY_EINT_PIN__>;
slew-rate = <0>;
bias-pull-up = <00>;
};
};
};
&keypad{
pinctrl-names = "default", "mc_key_as_int";
pinctrl-0 = <&mc_key_default>;
pinctrl-1 = <&mc_key_as_int>;
};
1.2 获取dts配置值
#ifdef CUST_EINT_WPS_PIN
#define CUST_EINT_KPD_PWRKEY_MAP KEY_F1
struct device_node *node;
int key_mc_irq;
u32 intmc[2] = {0, 0};
struct pinctrl *key_mc_pinctrl;
struct pinctrl_state *key_mc_set_input;
static u8 kpd_wpskey_state = !IRQ_TYPE_LEVEL_LOW;
static void kpd_wpskey_handler(unsigned long data);
static DECLARE_TASKLET(kpd_wpskey_tasklet, kpd_wpskey_handler, 0);
int mc_key_get_gpio_info(struct platform_device *pdev){ //采用平台节点
int ret;
//printk("[%s] key_mc_pinctrl+++++++++++++++++\n",pdev->name);
key_mc_pinctrl = devm_pinctrl_get(&pdev->dev);
key_mc_set_input = pinctrl_lookup_state(key_mc_pinctrl, "mc_key_as_int");
if (IS_ERR(key_mc_set_input)) {
ret = PTR_ERR(key_mc_set_input);
dev_err(&pdev->dev, " Cannot find key mc pinctrl key_mc_pinctrl!\n");
return ret;
}
if(!IS_ERR(key_mc_set_input)){
pinctrl_select_state(key_mc_pinctrl, key_mc_set_input);
}
//printk("[%s] key_mc_pinctrl+++++++++++++++++\n",pdev->name);
return 0;
}
int eint_mc_init(void){
//printk("%s, mc_key start\n",__func__);
node = of_find_compatible_node(NULL, NULL, "mediatek,mc_key");
if(node) {
of_property_read_u32_array(node, "debounce", intmc, ARRAY_SIZE(intmc));
gpio_set_debounce(intmc[0], intmc[1]);
key_mc_irq = irq_of_parse_and_map(node, 0);
if(request_irq(key_mc_irq, kpd_wpskey_eint_handler, IRQ_TYPE_EDGE_FALLING, "P_KEY", NULL)){
//printk(KERN_ERR "key_mc_irq EXAMPLE KEY_MC_IRQ LINE NOT AVAILABLE!!\n");
return -EINVAL;
}
enable_irq(key_mc_irq);
__set_bit(CUST_EINT_KPD_PWRKEY_MAP, kpd_input_dev->keybit);
//wps_set_init();
}
return 0;
}
1.3 软件代码
void kpd_wpskey_handler_hal(unsigned long data){
bool pressed;
kpd_wpskey_state = !kpd_wpskey_state;
pressed = (kpd_wpskey_state == !!IRQ_TYPE_LEVEL_LOW);
printk(KPD_SAY "(%s) HW keycode = using wps EINT\n",
pressed ? "pressed" : "released");
input_report_key(kpd_input_dev, CUST_EINT_KPD_PWRKEY_MAP, pressed);
input_sync(kpd_input_dev);
//wps_report(pressed);
printk("report Linux keycode = %u\n", CUST_EINT_KPD_PWRKEY_MAP);
/* for detecting the return to old_state */
if(pressed){
irq_set_irq_type(key_mc_irq, IRQ_TYPE_LEVEL_HIGH);
}else{
irq_set_irq_type(key_mc_irq, IRQ_TYPE_LEVEL_LOW);
}
enable_irq(key_mc_irq);
}
static void kpd_wpskey_handler(unsigned long data)
{
kpd_wpskey_handler_hal(data);
}
irqreturn_t kpd_wpskey_eint_handler(int irq, void *dev_id)
{
disable_irq_nosync(key_mc_irq);
tasklet_schedule(&kpd_wpskey_tasklet);
return IRQ_HANDLED;
}
轮询方式 无中断的偏码器轮询
1.1 dts 配置
#define __ZKT_ENCODER_A_PIN__ PINMUX_GPIO34__FUNC_GPIO34 //PINMUX_GPIO30__FUNC_GPIO30//PINMUX_GPIO34__FUNC_GPIO34
#define __ZKT_ENCODER_B_PIN__ PINMUX_GPIO35__FUNC_GPIO35 //PINMUX_GPIO30__FUNC_GPIO30//PINMUX_GPIO35__FUNC_GPIO35
#define __ZKT_ENCODER_C_PIN__ PINMUX_GPIO32__FUNC_GPIO32
#define __ZKT_ENCODER_D_PIN__ PINMUX_GPIO33__FUNC_GPIO33
&keypad {
pinctrl-names = "pin_default", "zkt_encoder_a_input","zkt_encoder_b_input","zkt_encoder_c_input",
"zkt_encoder_d_input","zkt_speaker_output0";
pinctrl-0 = <&zkt_pin_default>;
pinctrl-1 = <&zkt_encoder_a_input>;
pinctrl-2 = <&zkt_encoder_b_input>;
pinctrl-3 = <&zkt_encoder_c_input>;
pinctrl-4 = <&zkt_encoder_d_input>;
pinctrl-5 = <&zkt_speaker_output0>;
};
&pio {
zkt_pin_default: zkt_pin_default@0 {
};
zkt_encoder_a_input: zkt_pin@1 {
pins_cmd_dat {
pins = <__ZKT_ENCODER_A_PIN__>;
slew-rate = <0>;
bias-disable;
};
};
zkt_encoder_b_input: zkt_pin@2 {
pins_cmd_dat {
pins = <__ZKT_ENCODER_B_PIN__>;
slew-rate = <0>;
bias-disable;
};
};
zkt_encoder_c_input: zkt_pin@3 {
pins_cmd_dat {
pins = <__ZKT_ENCODER_C_PIN__>;
slew-rate = <0>;
bias-disable;
};
};
zkt_encoder_d_input: zkt_pin@4 {
pins_cmd_dat {
pins = <__ZKT_ENCODER_D_PIN__>;
slew-rate = <0>;
bias-disable;
};
};
zkt_speaker_output0: zkt_pin@5 {
pins_cmd_dat {
pins = <__ZKT_SPEARER_PIN__>;
slew-rate = <1>;
output-low;
};
};
zkt_speaker_output1: zkt_pin@56{
pins_cmd_dat {
pins = <__ZKT_SPEARER_PIN__>;
slew-rate = <1>;
output-high;
};
};
};
1.2 dts解析
struct pinctrl *zktpinctrl1;
struct pinctrl_state *zkt_encoder_input0, *zkt_encoder_input1, //as input
*zkt_encoder_input2, *zkt_encoder_input3; //as input
int zkt_key_get_gpio_info(struct platform_device *pdev) //采用平台节点
{
int ret;
//printk("zktpinctrl+++++++++++++++++\n");
zktpinctrl1 = devm_pinctrl_get(&pdev->dev);
if (IS_ERR(zktpinctrl1)) {
ret = PTR_ERR(zktpinctrl1);
dev_err(&pdev->dev, "fwq Cannot find mc led zktpinctrl1!\n");
return ret;
}
zkt_encoder_input0 = pinctrl_lookup_state(zktpinctrl1, "zkt_encoder_a_input");
if (IS_ERR(zkt_encoder_input0)) {
ret = PTR_ERR(zkt_encoder_input0);
dev_err(&pdev->dev, "zkt Cannot find led pinctrl zkt_encoder_input0!\n");
return ret;
}
zkt_encoder_input1 = pinctrl_lookup_state(zktpinctrl1, "zkt_encoder_b_input");
if (IS_ERR(zkt_encoder_input1)) {
ret = PTR_ERR(zkt_encoder_input1);
dev_err(&pdev->dev, "zkt Cannot find led pinctrl zkt_encoder_input1!\n");
return ret;
}
zkt_encoder_input2 = pinctrl_lookup_state(zktpinctrl1, "zkt_encoder_c_input");
if (IS_ERR(zkt_encoder_input2)) {
ret = PTR_ERR(zkt_encoder_input2);
dev_err(&pdev->dev, "zkt Cannot find led pinctrl zkt_encoder_input2!\n");
return ret;
}
zkt_encoder_input3 = pinctrl_lookup_state(zktpinctrl1, "zkt_encoder_d_input");
if (IS_ERR(zkt_encoder_input3)) {
ret = PTR_ERR(zkt_encoder_input3);
dev_err(&pdev->dev, "zkt Cannot find led pinctrl zkt_encoder_input3!\n");
return ret;
}
return 0;
}
1.3 软件代码
static struct timer_list buttons_timer;
static unsigned int pinval=0x0f,pinvalold=0xff;
static unsigned int key_val=KEY_1;
static unsigned int key_table[]={
9,//5,
1,//4,
6,//10,
12,//3,
10,//9,
2,//8,
5,//6,
13,//7,
15,//15,
11,//13,
0,//0,
4,//2,
14,//11,
8,//1,
3,//12,
7,//14
};
static unsigned int key_report_val_table[] = {
KEY_1,//1
KEY_2,//2
KEY_3,//3
KEY_4,//4
KEY_5,//5
KEY_6,//6
KEY_7,//7
KEY_8,//8
KEY_9,//9
KEY_A,//10
KEY_B,//11
KEY_C,//12
KEY_D,//13
KEY_E,//14
KEY_F,//15
KEY_G,//16
};
void input_report_encoder(unsigned int temp)
{
unsigned int i,key_report_val;
for(i=0;i<(sizeof(key_table)/4);i++) //按键转换需要的值
{
if(temp==key_table[i])
{
break;
}
}
key_report_val=key_report_val_table[i]; //上报
key_val=key_report_val;
input_report_key(kpd_input_dev, key_report_val, 1);
input_sync(kpd_input_dev);
input_report_key(kpd_input_dev, key_report_val, 0);
input_sync(kpd_input_dev);
}
#define GPIO_ENCODER_A_PIN 34//GPIO30
#define GPIO_ENCODER_B_PIN 35//GPIO31
#define GPIO_ENCODER_C_PIN 32//GPIO32
#define GPIO_ENCODER_D_PIN 33//GPIO33
static void buttons_timer_function(unsigned long data)
{
unsigned int pinvala=0,pinvalb=0,pinvalc=0,pinvald=0;
//读键值
pinvala = gpio_get_value(GPIO_ENCODER_A_PIN);
pinvalb = gpio_get_value(GPIO_ENCODER_B_PIN);
pinvalc = gpio_get_value(GPIO_ENCODER_C_PIN);
pinvald = gpio_get_value(GPIO_ENCODER_D_PIN);
//printk("[ENCODER] buttons_timer_function\n");
//printk("[ENCODER] pinvala = %d ,pinvalb = %d ,pinvalc = %d ,pinvald = %d \n", pinvala, pinvalb,pinvalc,pinvald);
pinval = (pinvala|(pinvalb<<1)|(pinvalc<<2)|(pinvald<<3));
//printk("[ENCODER] pinval = %d \n",pinval);
//printk("[ENCODER] key_report_val_table = %d ,key_table = %d \n", (sizeof(key_report_val_table)/4), (sizeof(key_table)/4));
if(pinval!=pinvalold){
input_report_encoder(pinval);//上报
}
pinvalold=pinval;
mod_timer(&buttons_timer, jiffies + (HZ /5));
//buttons_timer=jiffies + (HZ /10);
//add_timer(&buttons_timer);
}
void encoder_driver_init(void)
{
unsigned int i ;
if((!IS_ERR(zkt_encoder_input0))&(!IS_ERR(zkt_encoder_input1))&(!IS_ERR(zkt_encoder_input2))&(!IS_ERR(zkt_encoder_input3))){
pinctrl_select_state(zktpinctrl1, zkt_encoder_input0);
pinctrl_select_state(zktpinctrl1, zkt_encoder_input1);
pinctrl_select_state(zktpinctrl1, zkt_encoder_input2);
pinctrl_select_state(zktpinctrl1, zkt_encoder_input3);
}
for(i=0;i<(sizeof(key_report_val_table)/4);i++) //unsigned int 4个字节
{
__set_bit(key_report_val_table[i], kpd_input_dev->keybit); //set_bit()告诉input输入子系统支持哪些事件,哪些按键。例如:
}
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);
/* 修改定时器定时时间,定时10ms,即10秒后启动定时器
* HZ 表示100个jiffies,1 jiffies的单位为10ms,即HZ = 100*10ms = 1s
* 这里HZ/10即定时100ms
*/
mod_timer(&buttons_timer, jiffies + (HZ /1));
}
五:调试案例
案例一: 采用外部扩展按键 按键无反应
现象: 所有按键无反应
平台: android6.0,MTK6737M
步骤:
1.1 查看dws .配置正确
1.2 查看input注册
getevent -i /dev/input/event0
有注册
1.3 查看input设备上报
getevent -l /dev/input/event0
无上报
1.4 查看input中断有无触发
cat /proc/interrupts | grep "mtk-kpd"
无触发
1.5 查看GPIO 寄存器
cat /sys/devices/virtual/misc/mtgpio/pin
正确
1.6 按Column键触发 GPIO 寄存器是否有变化
cat /sys/devices/virtual/misc/mtgpio/pin
无变化 ,怀疑是硬件问题
1.7 用万用表量 Column GPIO 口
无变化 确定是硬件问题
经硬件检测,是在Column增加了一个1K限流电阻.在不扩展的的按键是正常能电压变化
但扩展后, 电压给限制了,不变化,换0欧电阻,正常。
案例二: 某个按键无反应
现象: 按键无反应。
修改 代码
static int kpd_pdrv_probe(struct platform_device *pdev)
{
...
+#if 0
#if defined(CONFIG_KPD_PWRKEY_USE_EINT) || defined(CONFIG_KPD_PWRKEY_USE_PMIC)
__set_bit(kpd_dts_data.kpd_sw_pwrkey, kpd_input_dev->keybit);
kpd_keymap[8] = 0; //会关了支持一些按键 正常的都会是9*n都是power键
#endif
if (!kpd_dts_data.kpd_use_extend_type) {
for (i = 17; i < KPD_NUM_KEYS; i += 9) /* only [8] works for Power key */
kpd_keymap[i] = 0;
}
+#endif
for (i = 0; i < KPD_NUM_KEYS; i++) {
if (kpd_keymap[i] != 0)
__set_bit(kpd_keymap[i], kpd_input_dev->keybit);
}
...
}
或者在Column7上不要配置按键