linux驱动学习2(kpd驱动初步分析)
生活随笔
收集整理的這篇文章主要介紹了
linux驱动学习2(kpd驱动初步分析)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、kpd_pdrv_probe函數的分析:
/*1. 輸入設備實例 kpd_input_dev */ 全局變量:static struct input_dev *kpd_input_dev; static int kpd_pdrv_probe(struct platform_device *pdev) {int i, r;u16 new_state[KPD_NUM_MEMS];/* initialize and register input device (/dev/input/eventX) *//*2. 初始化輸入設備并分配內存空間*/kpd_input_dev = input_allocate_device(); if (!kpd_input_dev)return -ENOMEM;/*下面開始填充kpd_input_dev 設備驅動結構體*/kpd_input_dev->name = KPD_NAME;kpd_input_dev->id.bustype = BUS_HOST;kpd_input_dev->id.vendor = 0x2454;kpd_input_dev->id.product = 0x6575;kpd_input_dev->id.version = 0x0010;kpd_input_dev->open = kpd_open;/*3. 設置某位為1,以第二個參數為起始地址,EV_KEY表示要設置的位作用:告訴input子系統支持那些事件, EV_KEY 這里表示告訴input子系統支持按鍵事件*/__set_bit(EV_KEY, kpd_input_dev->evbit); #if (KPD_PWRKEY_USE_EINT||KPD_PWRKEY_USE_PMIC)/*4. 設置某位為1,以第二個參數為起始地址,EV_KEY表示要設置的位作用:告訴input子系統支持那些按鍵, KPD_PWRKEY_MAP 這里表示告訴input子系統支持電源按鍵*/__set_bit(KPD_PWRKEY_MAP, kpd_input_dev->keybit);kpd_keymap[8] = 0; #endiffor (i = 17; i < KPD_NUM_KEYS; i += 9) /* only [8] works for Power key */kpd_keymap[i] = 0;for (i = 0; i < KPD_NUM_KEYS; i++) {if (kpd_keymap[i] != 0)__set_bit(kpd_keymap[i], kpd_input_dev->keybit);}/*5. 上述幾行代碼表示設置電源按鍵 kpd_keymap 為0,其它按鍵 kpd_keymap 為1*/__set_bit(250, kpd_input_dev->keybit);__set_bit(251, kpd_input_dev->keybit);#if KPD_AUTOTESTfor (i = 0; i < ARRAY_SIZE(kpd_auto_keymap); i++)__set_bit(kpd_auto_keymap[i], kpd_input_dev->keybit); #endif#if KPD_HAS_SLIDE_QWERTY__set_bit(EV_SW, kpd_input_dev->evbit);__set_bit(SW_LID, kpd_input_dev->swbit);__set_bit(SW_LID, kpd_input_dev->sw); /* 1: lid shut => closed */ #endif#ifdef KPD_PMIC_RSTKEY_MAP__set_bit(KPD_PMIC_RSTKEY_MAP, kpd_input_dev->keybit); #endif/*6. 指定kpd_input_dev這個平臺設備sysfs中的父設備節點*/kpd_input_dev->dev.parent = &pdev->dev; /*7. 注冊input輸入子系統*/r = input_register_device(kpd_input_dev); if (r) {printk(KPD_SAY "register input device failed (%d)\n", r);input_free_device(kpd_input_dev);return r;}/* register device (/dev/mt6575-kpd) *//*7. 指定kpd_dev這個平臺設備sysfs中的父設備節點*/kpd_dev.parent = &pdev->dev; /*8. 注冊混雜設備*/r = misc_register(&kpd_dev); if (r) {printk(KPD_SAY "register device failed (%d)\n", r);input_unregister_device(kpd_input_dev);return r;}/*8. 注冊按鍵中斷*//* register IRQ and EINT *//*9. 設置消抖時間*/kpd_set_debounce(KPD_KEY_DEBOUNCE); /*10. 設置中斷觸發方式*/mt65xx_irq_set_sens(MT6575_KP_IRQ_ID, MT65xx_EDGE_SENSITIVE); /*11 . 設置中斷優先級*/mt65xx_irq_set_polarity(MT6575_KP_IRQ_ID, MT65xx_POLARITY_LOW); /*12. 注冊中斷處理函數*/r = request_irq(MT6575_KP_IRQ_ID, kpd_irq_handler, 0, KPD_NAME, NULL); if (r) {printk(KPD_SAY "register IRQ failed (%d)\n", r);misc_deregister(&kpd_dev);input_unregister_device(kpd_input_dev);return r;}/*13. 以下為電源鍵中斷函數的注冊*/ #if KPD_PWRKEY_USE_EINTmt65xx_eint_set_sens(KPD_PWRKEY_EINT, KPD_PWRKEY_SENSITIVE);mt65xx_eint_set_hw_debounce(KPD_PWRKEY_EINT, KPD_PWRKEY_DEBOUNCE);mt65xx_eint_registration(KPD_PWRKEY_EINT, true, KPD_PWRKEY_POLARITY,kpd_pwrkey_eint_handler, false); #endifif(kpd_enable_lprst && get_boot_mode() == NORMAL_BOOT) {kpd_print("Normal Boot\n"); #ifdef KPD_PMIC_LPRST_TDkpd_print("Enable LPRST\n");/*14. 以下為設置按鍵喚醒的時間*/upmu_testmode_pwrkey_rst_en(0x01);upmu_testmode_homekey_rst_en(0x01);upmu_testmode_pwrkey_rst_td(KPD_PMIC_LPRST_TD); #endif} else {kpd_print("Disable LPRST %d\n", kpd_enable_lprst);}/*15. 設置一個高精度定時器,并且定義了時間到期的回調函數 aee_timer_func*/hrtimer_init(&aee_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);aee_timer.function = aee_timer_func;/*以下為三個按鍵的初始化,也就是配置注意,默認值gpio輸出是0*/ #if 1 // ylliu add. dct default value does not work.../* KCOL0: GPIO103: KCOL1: GPIO108, KCOL2: GPIO105, KCOL4: GPIO102 input + pull enable + pull up */mt_set_gpio_mode(GPIO_KPD_KCOL0_PIN, GPIO_KPD_KCOL0_PIN_M_KP_COL);mt_set_gpio_dir(GPIO_KPD_KCOL0_PIN, GPIO_DIR_IN);mt_set_gpio_pull_enable(GPIO_KPD_KCOL0_PIN, GPIO_PULL_ENABLE);mt_set_gpio_pull_select(GPIO_KPD_KCOL0_PIN, GPIO_PULL_UP);mt_set_gpio_mode(GPIO_KPD_KCOL1_PIN, GPIO_KPD_KCOL1_PIN_M_KP_COL);mt_set_gpio_dir(GPIO_KPD_KCOL1_PIN, GPIO_DIR_IN);mt_set_gpio_pull_enable(GPIO_KPD_KCOL1_PIN, GPIO_PULL_ENABLE);mt_set_gpio_pull_select(GPIO_KPD_KCOL1_PIN, GPIO_PULL_UP);mt_set_gpio_mode(GPIO_KPD_KCOL2_PIN, GPIO_KPD_KCOL2_PIN_M_KP_COL);mt_set_gpio_dir(GPIO_KPD_KCOL2_PIN, GPIO_DIR_IN);mt_set_gpio_pull_enable(GPIO_KPD_KCOL2_PIN, GPIO_PULL_ENABLE);mt_set_gpio_pull_select(GPIO_KPD_KCOL2_PIN, GPIO_PULL_UP);mt_set_gpio_mode(GPIO_KPD_KCOL4_PIN, GPIO_KPD_KCOL4_PIN_M_KP_COL);mt_set_gpio_dir(GPIO_KPD_KCOL4_PIN, GPIO_DIR_IN);mt_set_gpio_pull_enable(GPIO_KPD_KCOL4_PIN, GPIO_PULL_ENABLE);mt_set_gpio_pull_select(GPIO_KPD_KCOL4_PIN, GPIO_PULL_UP);/* KROW0: GPIO98, KROW1: GPIO97: KROW2: GPIO95 output + pull disable + pull down */mt_set_gpio_mode(GPIO_KPD_KROW0_PIN, GPIO_KPD_KROW0_PIN_M_KP_ROW);mt_set_gpio_dir(GPIO_KPD_KROW0_PIN, GPIO_DIR_OUT);mt_set_gpio_pull_enable(GPIO_KPD_KROW0_PIN, GPIO_PULL_DISABLE); mt_set_gpio_pull_select(GPIO_KPD_KROW0_PIN, GPIO_PULL_DOWN);// mt_set_gpio_mode(97, 1); // mt_set_gpio_dir(97, 1); // mt_set_gpio_pull_enable(97, 0); // mt_set_gpio_pull_select(97, 0); // // mt_set_gpio_mode(95, 1); // mt_set_gpio_dir(95, 1); // mt_set_gpio_pull_enable(95, 0); // mt_set_gpio_pull_select(95, 0); #endif// default disable backlight. reboot from recovery need this.kpd_disable_backlight();// store default state, resolve recovery bugs.kpd_get_keymap_state(new_state);memcpy(kpd_keymap_state, new_state, sizeof(new_state));return 0; }
?
?
二、當執行完面probe函數進行相關初始化后,這時候,當我們按鍵按下了,就會觸發中斷,進入中斷服務子程序
static irqreturn_t __tcmfunc kpd_irq_handler(int irq, void *dev_id) {/* use _nosync to avoid deadlock */disable_irq_nosync(MT6575_KP_IRQ_ID);tasklet_schedule(&kpd_keymap_tasklet);return IRQ_HANDLED; }
可以看到,中斷服務程序里面執行了 tasklet_schedule(&kpd_keymap_tasklet);
跟蹤代碼可以發現,實際上是執行了這個函數kpd_keymap_handler,下面仔細分析
這個函數,詳細注釋如下:
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); //首先讀取鍵值,并且存放于new_state中if (pmic_get_acc_state() == 1) {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; //i = 0, j = 1; //這里是得到hw_keycode的值printk("hw_keycode = %d ,i = %d, j = %d \n",hw_keycode,i,j);/* bit is 1: not pressed, 0: pressed */pressed = !(new_state[i] & mask); //(new_state[i] & mask) = 0if (kpd_show_hw_keycode) {printk(KPD_SAY "(%s) HW keycode = %u\n",pressed ? "pressed" : "released",hw_keycode);}BUG_ON(hw_keycode >= KPD_NUM_KEYS);linux_keycode = kpd_keymap[hw_keycode]; //這里的linux_keycode恒為零。printk("linux_keycode = %d \n",linux_keycode);if(unlikely(linux_keycode == 0)) {if (hw_keycode == 1 && pressed) { // special key, SOS.struct device *dev = &(kpd_input_dev->dev);char *envp[] = { "SOS_pressed", NULL };kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); //建立設備文件?printk(KPD_SAY "SOS_pressed\n");// used by recovery./*這個接口會向INPUT子系統上報按鍵(該按鍵被按下)*/input_report_key(kpd_input_dev, 251, pressed); //如果上層檢測到SOS_pressed就會做相應處理。 } else if (hw_keycode == 2 && pressed) { // special key, background.struct device *dev = &(kpd_input_dev->dev);char *envp[] = { "background_pressed", NULL };kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);printk(KPD_SAY "background_pressed\n");// used by recovery.input_report_key(kpd_input_dev, 8, pressed);} else if (hw_keycode == 4 && pressed) { // special key, mode.struct device *dev = &(kpd_input_dev->dev);char *envp[] = { "mode_pressed", NULL };kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);printk(KPD_SAY "mode_pressed\n");} else if (hw_keycode == 1 || hw_keycode == 2 || hw_keycode == 4) { // add this to turn off backlight.printk(KPD_SAY "background or SOS or mode release!\n");// used by recovery.if (hw_keycode == 1)input_report_key(kpd_input_dev, 251, pressed);else if (hw_keycode == 2)input_report_key(kpd_input_dev, 8, pressed);} else {kpd_print("Linux keycode = 0\n");continue;}}kpd_aee_handler(linux_keycode, pressed); kpd_backlight_handler(pressed, linux_keycode);input_report_key(kpd_input_dev, linux_keycode, pressed);}}} else {printk(KPD_SAY "acc off, ignore and key...\n");}memcpy(kpd_keymap_state, new_state, sizeof(new_state));kpd_print("save new keymap state\n");enable_irq(MT6575_KP_IRQ_ID); }
三、kpd_aee_handler函數分析
static void kpd_aee_handler(u32 keycode, u16 pressed) {if(pressed) {if(keycode == KEY_VOLUMEUP) {__set_bit(0, &aee_pressed_keys);} else if(keycode == KEY_VOLUMEDOWN) {__set_bit(1, &aee_pressed_keys);} else {return;}kpd_update_aee_state();} else {if(keycode == KEY_VOLUMEUP) {__clear_bit(0, &aee_pressed_keys);} else if(keycode == KEY_VOLUMEDOWN) {__clear_bit(1, &aee_pressed_keys);} else {return;}kpd_update_aee_state();} }
詳細分析:
1.__set_bit(0, &aee_pressed_keys),定義了一個:static u16 aee_pressed_keys;
所以__set_bit的意思是將aee_pressed_keys的bit0設置為1
2.相應的__clear_bit(0, &aee_pressed_keys);就是把aee_pressed_keys的bit0清零,
3.還有在內核的non-atomic.h文件中還有一些其它的位操作,記住__set_bit和set_bit的區別就
是前者是非原子操作,而后者是原子操作,所謂原子操作,意思是最小的執行單位,再其執行過
程中是不會被其他任務打斷的。
四、背光處理函數
void kpd_backlight_handler(bool pressed, u16 linux_keycode) { if (kpd_suspend && !test_bit(linux_keycode, kpd_wake_keybit)) {kpd_print("Linux keycode %u is not WAKE key\n", linux_keycode);return;}/* not in suspend or the key pressed is WAKE key */if (pressed) {atomic_inc(&kpd_key_pressed);kpd_backlight_on = !!atomic_read(&kpd_key_pressed);schedule_work(&kpd_backlight_work); //點亮背光燈kpd_print("switch backlight on\n");} else {atomic_dec(&kpd_key_pressed);mod_timer(&kpd_backlight_timer, //KPD_BACKLIGHT_TIME控制背光時間,單位為sec,如果注釋掉這句,背光將不滅jiffies + KPD_BACKLIGHT_TIME * HZ);kpd_print("activate backlight timer\n");} }
詳細分析
1.首先用到了一個位操作函數,注意這個函數是原子操作test_bit
2.全局變量static atomic_t kpd_key_pressed = ATOMIC_INIT(0);這是原子操作的初始化,kpd_key_pressed初始化為0
3.上述函數涉及到一些原子操作函數,解釋如下:
atomic_inc(&kpd_key_pressed); 是對變量進行加1操作
atomic_dec(&kpd_key_pressed); 是對變量進行減1操作
!!atomic_read(&kpd_key_pressed);是讀取變量的值,前面兩個 !!強調該返回值不是1就是0:bool類型
4.mod_timer:該函數的作用是修改一個已經調度的定時器結構的到期時間。
五、背光控制函數
調度的是這個函數
static void kpd_switch_backlight(struct work_struct *work) {if (kpd_backlight_on) {kpd_enable_backlight();kpd_print("backlight is on\n");} else {kpd_disable_backlight();kpd_print("backlight is off\n");} }
這里就能夠看到使能和失能背光的函數,繼續跟蹤:
void kpd_enable_backlight(void) {/*mt6326_kpled_dim_duty_Full();mt6326_kpled_Enable();*/upmu_kpled_dim_duty(31); upmu_kpled_en(1); } upmu_kpled_dim_duty這是控制背光電流大小從而可以控制亮度 upmu_kpled_en這是控制開關。
kpd驅動初步分析完畢。
/*1. 輸入設備實例 kpd_input_dev */ 全局變量:static struct input_dev *kpd_input_dev; static int kpd_pdrv_probe(struct platform_device *pdev) {int i, r;u16 new_state[KPD_NUM_MEMS];/* initialize and register input device (/dev/input/eventX) *//*2. 初始化輸入設備并分配內存空間*/kpd_input_dev = input_allocate_device(); if (!kpd_input_dev)return -ENOMEM;/*下面開始填充kpd_input_dev 設備驅動結構體*/kpd_input_dev->name = KPD_NAME;kpd_input_dev->id.bustype = BUS_HOST;kpd_input_dev->id.vendor = 0x2454;kpd_input_dev->id.product = 0x6575;kpd_input_dev->id.version = 0x0010;kpd_input_dev->open = kpd_open;/*3. 設置某位為1,以第二個參數為起始地址,EV_KEY表示要設置的位作用:告訴input子系統支持那些事件, EV_KEY 這里表示告訴input子系統支持按鍵事件*/__set_bit(EV_KEY, kpd_input_dev->evbit); #if (KPD_PWRKEY_USE_EINT||KPD_PWRKEY_USE_PMIC)/*4. 設置某位為1,以第二個參數為起始地址,EV_KEY表示要設置的位作用:告訴input子系統支持那些按鍵, KPD_PWRKEY_MAP 這里表示告訴input子系統支持電源按鍵*/__set_bit(KPD_PWRKEY_MAP, kpd_input_dev->keybit);kpd_keymap[8] = 0; #endiffor (i = 17; i < KPD_NUM_KEYS; i += 9) /* only [8] works for Power key */kpd_keymap[i] = 0;for (i = 0; i < KPD_NUM_KEYS; i++) {if (kpd_keymap[i] != 0)__set_bit(kpd_keymap[i], kpd_input_dev->keybit);}/*5. 上述幾行代碼表示設置電源按鍵 kpd_keymap 為0,其它按鍵 kpd_keymap 為1*/__set_bit(250, kpd_input_dev->keybit);__set_bit(251, kpd_input_dev->keybit);#if KPD_AUTOTESTfor (i = 0; i < ARRAY_SIZE(kpd_auto_keymap); i++)__set_bit(kpd_auto_keymap[i], kpd_input_dev->keybit); #endif#if KPD_HAS_SLIDE_QWERTY__set_bit(EV_SW, kpd_input_dev->evbit);__set_bit(SW_LID, kpd_input_dev->swbit);__set_bit(SW_LID, kpd_input_dev->sw); /* 1: lid shut => closed */ #endif#ifdef KPD_PMIC_RSTKEY_MAP__set_bit(KPD_PMIC_RSTKEY_MAP, kpd_input_dev->keybit); #endif/*6. 指定kpd_input_dev這個平臺設備sysfs中的父設備節點*/kpd_input_dev->dev.parent = &pdev->dev; /*7. 注冊input輸入子系統*/r = input_register_device(kpd_input_dev); if (r) {printk(KPD_SAY "register input device failed (%d)\n", r);input_free_device(kpd_input_dev);return r;}/* register device (/dev/mt6575-kpd) *//*7. 指定kpd_dev這個平臺設備sysfs中的父設備節點*/kpd_dev.parent = &pdev->dev; /*8. 注冊混雜設備*/r = misc_register(&kpd_dev); if (r) {printk(KPD_SAY "register device failed (%d)\n", r);input_unregister_device(kpd_input_dev);return r;}/*8. 注冊按鍵中斷*//* register IRQ and EINT *//*9. 設置消抖時間*/kpd_set_debounce(KPD_KEY_DEBOUNCE); /*10. 設置中斷觸發方式*/mt65xx_irq_set_sens(MT6575_KP_IRQ_ID, MT65xx_EDGE_SENSITIVE); /*11 . 設置中斷優先級*/mt65xx_irq_set_polarity(MT6575_KP_IRQ_ID, MT65xx_POLARITY_LOW); /*12. 注冊中斷處理函數*/r = request_irq(MT6575_KP_IRQ_ID, kpd_irq_handler, 0, KPD_NAME, NULL); if (r) {printk(KPD_SAY "register IRQ failed (%d)\n", r);misc_deregister(&kpd_dev);input_unregister_device(kpd_input_dev);return r;}/*13. 以下為電源鍵中斷函數的注冊*/ #if KPD_PWRKEY_USE_EINTmt65xx_eint_set_sens(KPD_PWRKEY_EINT, KPD_PWRKEY_SENSITIVE);mt65xx_eint_set_hw_debounce(KPD_PWRKEY_EINT, KPD_PWRKEY_DEBOUNCE);mt65xx_eint_registration(KPD_PWRKEY_EINT, true, KPD_PWRKEY_POLARITY,kpd_pwrkey_eint_handler, false); #endifif(kpd_enable_lprst && get_boot_mode() == NORMAL_BOOT) {kpd_print("Normal Boot\n"); #ifdef KPD_PMIC_LPRST_TDkpd_print("Enable LPRST\n");/*14. 以下為設置按鍵喚醒的時間*/upmu_testmode_pwrkey_rst_en(0x01);upmu_testmode_homekey_rst_en(0x01);upmu_testmode_pwrkey_rst_td(KPD_PMIC_LPRST_TD); #endif} else {kpd_print("Disable LPRST %d\n", kpd_enable_lprst);}/*15. 設置一個高精度定時器,并且定義了時間到期的回調函數 aee_timer_func*/hrtimer_init(&aee_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);aee_timer.function = aee_timer_func;/*以下為三個按鍵的初始化,也就是配置注意,默認值gpio輸出是0*/ #if 1 // ylliu add. dct default value does not work.../* KCOL0: GPIO103: KCOL1: GPIO108, KCOL2: GPIO105, KCOL4: GPIO102 input + pull enable + pull up */mt_set_gpio_mode(GPIO_KPD_KCOL0_PIN, GPIO_KPD_KCOL0_PIN_M_KP_COL);mt_set_gpio_dir(GPIO_KPD_KCOL0_PIN, GPIO_DIR_IN);mt_set_gpio_pull_enable(GPIO_KPD_KCOL0_PIN, GPIO_PULL_ENABLE);mt_set_gpio_pull_select(GPIO_KPD_KCOL0_PIN, GPIO_PULL_UP);mt_set_gpio_mode(GPIO_KPD_KCOL1_PIN, GPIO_KPD_KCOL1_PIN_M_KP_COL);mt_set_gpio_dir(GPIO_KPD_KCOL1_PIN, GPIO_DIR_IN);mt_set_gpio_pull_enable(GPIO_KPD_KCOL1_PIN, GPIO_PULL_ENABLE);mt_set_gpio_pull_select(GPIO_KPD_KCOL1_PIN, GPIO_PULL_UP);mt_set_gpio_mode(GPIO_KPD_KCOL2_PIN, GPIO_KPD_KCOL2_PIN_M_KP_COL);mt_set_gpio_dir(GPIO_KPD_KCOL2_PIN, GPIO_DIR_IN);mt_set_gpio_pull_enable(GPIO_KPD_KCOL2_PIN, GPIO_PULL_ENABLE);mt_set_gpio_pull_select(GPIO_KPD_KCOL2_PIN, GPIO_PULL_UP);mt_set_gpio_mode(GPIO_KPD_KCOL4_PIN, GPIO_KPD_KCOL4_PIN_M_KP_COL);mt_set_gpio_dir(GPIO_KPD_KCOL4_PIN, GPIO_DIR_IN);mt_set_gpio_pull_enable(GPIO_KPD_KCOL4_PIN, GPIO_PULL_ENABLE);mt_set_gpio_pull_select(GPIO_KPD_KCOL4_PIN, GPIO_PULL_UP);/* KROW0: GPIO98, KROW1: GPIO97: KROW2: GPIO95 output + pull disable + pull down */mt_set_gpio_mode(GPIO_KPD_KROW0_PIN, GPIO_KPD_KROW0_PIN_M_KP_ROW);mt_set_gpio_dir(GPIO_KPD_KROW0_PIN, GPIO_DIR_OUT);mt_set_gpio_pull_enable(GPIO_KPD_KROW0_PIN, GPIO_PULL_DISABLE); mt_set_gpio_pull_select(GPIO_KPD_KROW0_PIN, GPIO_PULL_DOWN);// mt_set_gpio_mode(97, 1); // mt_set_gpio_dir(97, 1); // mt_set_gpio_pull_enable(97, 0); // mt_set_gpio_pull_select(97, 0); // // mt_set_gpio_mode(95, 1); // mt_set_gpio_dir(95, 1); // mt_set_gpio_pull_enable(95, 0); // mt_set_gpio_pull_select(95, 0); #endif// default disable backlight. reboot from recovery need this.kpd_disable_backlight();// store default state, resolve recovery bugs.kpd_get_keymap_state(new_state);memcpy(kpd_keymap_state, new_state, sizeof(new_state));return 0; }
?
?
二、當執行完面probe函數進行相關初始化后,這時候,當我們按鍵按下了,就會觸發中斷,進入中斷服務子程序
static irqreturn_t __tcmfunc kpd_irq_handler(int irq, void *dev_id) {/* use _nosync to avoid deadlock */disable_irq_nosync(MT6575_KP_IRQ_ID);tasklet_schedule(&kpd_keymap_tasklet);return IRQ_HANDLED; }
可以看到,中斷服務程序里面執行了 tasklet_schedule(&kpd_keymap_tasklet);
跟蹤代碼可以發現,實際上是執行了這個函數kpd_keymap_handler,下面仔細分析
這個函數,詳細注釋如下:
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); //首先讀取鍵值,并且存放于new_state中if (pmic_get_acc_state() == 1) {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; //i = 0, j = 1; //這里是得到hw_keycode的值printk("hw_keycode = %d ,i = %d, j = %d \n",hw_keycode,i,j);/* bit is 1: not pressed, 0: pressed */pressed = !(new_state[i] & mask); //(new_state[i] & mask) = 0if (kpd_show_hw_keycode) {printk(KPD_SAY "(%s) HW keycode = %u\n",pressed ? "pressed" : "released",hw_keycode);}BUG_ON(hw_keycode >= KPD_NUM_KEYS);linux_keycode = kpd_keymap[hw_keycode]; //這里的linux_keycode恒為零。printk("linux_keycode = %d \n",linux_keycode);if(unlikely(linux_keycode == 0)) {if (hw_keycode == 1 && pressed) { // special key, SOS.struct device *dev = &(kpd_input_dev->dev);char *envp[] = { "SOS_pressed", NULL };kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); //建立設備文件?printk(KPD_SAY "SOS_pressed\n");// used by recovery./*這個接口會向INPUT子系統上報按鍵(該按鍵被按下)*/input_report_key(kpd_input_dev, 251, pressed); //如果上層檢測到SOS_pressed就會做相應處理。 } else if (hw_keycode == 2 && pressed) { // special key, background.struct device *dev = &(kpd_input_dev->dev);char *envp[] = { "background_pressed", NULL };kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);printk(KPD_SAY "background_pressed\n");// used by recovery.input_report_key(kpd_input_dev, 8, pressed);} else if (hw_keycode == 4 && pressed) { // special key, mode.struct device *dev = &(kpd_input_dev->dev);char *envp[] = { "mode_pressed", NULL };kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);printk(KPD_SAY "mode_pressed\n");} else if (hw_keycode == 1 || hw_keycode == 2 || hw_keycode == 4) { // add this to turn off backlight.printk(KPD_SAY "background or SOS or mode release!\n");// used by recovery.if (hw_keycode == 1)input_report_key(kpd_input_dev, 251, pressed);else if (hw_keycode == 2)input_report_key(kpd_input_dev, 8, pressed);} else {kpd_print("Linux keycode = 0\n");continue;}}kpd_aee_handler(linux_keycode, pressed); kpd_backlight_handler(pressed, linux_keycode);input_report_key(kpd_input_dev, linux_keycode, pressed);}}} else {printk(KPD_SAY "acc off, ignore and key...\n");}memcpy(kpd_keymap_state, new_state, sizeof(new_state));kpd_print("save new keymap state\n");enable_irq(MT6575_KP_IRQ_ID); }
三、kpd_aee_handler函數分析
static void kpd_aee_handler(u32 keycode, u16 pressed) {if(pressed) {if(keycode == KEY_VOLUMEUP) {__set_bit(0, &aee_pressed_keys);} else if(keycode == KEY_VOLUMEDOWN) {__set_bit(1, &aee_pressed_keys);} else {return;}kpd_update_aee_state();} else {if(keycode == KEY_VOLUMEUP) {__clear_bit(0, &aee_pressed_keys);} else if(keycode == KEY_VOLUMEDOWN) {__clear_bit(1, &aee_pressed_keys);} else {return;}kpd_update_aee_state();} }
詳細分析:
1.__set_bit(0, &aee_pressed_keys),定義了一個:static u16 aee_pressed_keys;
所以__set_bit的意思是將aee_pressed_keys的bit0設置為1
2.相應的__clear_bit(0, &aee_pressed_keys);就是把aee_pressed_keys的bit0清零,
3.還有在內核的non-atomic.h文件中還有一些其它的位操作,記住__set_bit和set_bit的區別就
是前者是非原子操作,而后者是原子操作,所謂原子操作,意思是最小的執行單位,再其執行過
程中是不會被其他任務打斷的。
四、背光處理函數
void kpd_backlight_handler(bool pressed, u16 linux_keycode) { if (kpd_suspend && !test_bit(linux_keycode, kpd_wake_keybit)) {kpd_print("Linux keycode %u is not WAKE key\n", linux_keycode);return;}/* not in suspend or the key pressed is WAKE key */if (pressed) {atomic_inc(&kpd_key_pressed);kpd_backlight_on = !!atomic_read(&kpd_key_pressed);schedule_work(&kpd_backlight_work); //點亮背光燈kpd_print("switch backlight on\n");} else {atomic_dec(&kpd_key_pressed);mod_timer(&kpd_backlight_timer, //KPD_BACKLIGHT_TIME控制背光時間,單位為sec,如果注釋掉這句,背光將不滅jiffies + KPD_BACKLIGHT_TIME * HZ);kpd_print("activate backlight timer\n");} }
詳細分析
1.首先用到了一個位操作函數,注意這個函數是原子操作test_bit
2.全局變量static atomic_t kpd_key_pressed = ATOMIC_INIT(0);這是原子操作的初始化,kpd_key_pressed初始化為0
3.上述函數涉及到一些原子操作函數,解釋如下:
atomic_inc(&kpd_key_pressed); 是對變量進行加1操作
atomic_dec(&kpd_key_pressed); 是對變量進行減1操作
!!atomic_read(&kpd_key_pressed);是讀取變量的值,前面兩個 !!強調該返回值不是1就是0:bool類型
4.mod_timer:該函數的作用是修改一個已經調度的定時器結構的到期時間。
五、背光控制函數
調度的是這個函數
static void kpd_switch_backlight(struct work_struct *work) {if (kpd_backlight_on) {kpd_enable_backlight();kpd_print("backlight is on\n");} else {kpd_disable_backlight();kpd_print("backlight is off\n");} }
這里就能夠看到使能和失能背光的函數,繼續跟蹤:
void kpd_enable_backlight(void) {/*mt6326_kpled_dim_duty_Full();mt6326_kpled_Enable();*/upmu_kpled_dim_duty(31); upmu_kpled_en(1); } upmu_kpled_dim_duty這是控制背光電流大小從而可以控制亮度 upmu_kpled_en這是控制開關。
mod_timer函數的補充
http://www.360doc.com/content/12/0510/11/6973384_210041084.shtml
kpd驅動初步分析完畢。
總結
以上是生活随笔為你收集整理的linux驱动学习2(kpd驱动初步分析)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【C】牛客网 编程入门训练138道题
- 下一篇: linux自动断网,adsl自动断网的解