| /* |
| * Copyright (C) 2010 MediaTek, Inc. |
| * |
| * Author: Terry Chang <terry.chang@mediatek.com> |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| |
| #define DEBUG 1 |
| |
| #include "kpd.h" |
| #ifdef CONFIG_PM_WAKELOCKS |
| #include <linux/pm_wakeup.h> |
| #else |
| #include <linux/wakelock.h> |
| #endif |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/of_irq.h> |
| #include <linux/clk.h> |
| #include <linux/debugfs.h> |
| #include <linux/pinctrl/consumer.h> |
| |
| //#ifdef OPLUS_FEATURE_TP_BASIC |
| //Fuchun.Liao@BSP.CHG.Basic 2017/12/10 add for key |
| #include <linux/proc_fs.h> |
| #include <linux/uaccess.h> |
| #include <linux/of_gpio.h> |
| //#include <soc/oppo/oppo_project.h> |
| //#endif /*OPLUS_FEATURE_TP_BASIC*/ |
| |
| //#if OPLUS_BUG_COMPATIBILITY |
| //2020/10/30 reocvery mode open cmdq function for reocvery FBE failed |
| #include <soc/oplus/system/oplus_project.h> |
| //#endif /*OPLUS_BUG_COMPATIBILITY*/ |
| |
| #define KPD_NAME "mtk-kpd" |
| |
| #ifdef CONFIG_LONG_PRESS_MODE_EN |
| struct timer_list Long_press_key_timer; |
| atomic_t vol_down_long_press_flag = ATOMIC_INIT(0); |
| #endif |
| //#ifdef OPLUS_FEATURE_TP_BASIC |
| /* Bin.Li@EXP.BSP.bootloader.bootflow, 2017/05/15, Add for keypad volume up and volume down */ |
| //#define KPD_HOME_NAME "mtk-kpd-home" |
| #define KPD_VOL_UP_NAME "mtk-kpd-vol-up" |
| #define KPD_VOL_DOWN_NAME "mtk-kpd-vol-down" |
| |
| #define KEY_LEVEL_DEFAULT 1 |
| |
| struct vol_info { |
| unsigned int vol_up_irq; |
| unsigned int vol_down_irq; |
| unsigned int vol_up_gpio; |
| unsigned int vol_down_gpio; |
| int vol_up_val; |
| int vol_down_val; |
| int vol_up_irq_enabled; |
| int vol_down_irq_enabled; |
| int vol_up_irq_type; |
| int vol_down_irq_type; |
| struct device *dev; |
| struct platform_device *pdev; |
| bool homekey_as_vol_up; |
| struct timer_list vol_up_timer; |
| unsigned int vol_up_delay; |
| struct timer_list vol_down_timer; |
| unsigned int vol_down_delay; |
| }vol_key_info; |
| |
| static irqreturn_t kpd_volumeup_irq_handler(int irq, void *dev_id); |
| static void kpd_volumeup_task_process(unsigned long data); |
| static irqreturn_t kpd_volumedown_irq_handler(int irq, void *dev_id); |
| static void kpd_volumedown_task_process(unsigned long data); |
| |
| #ifdef CONFIG_OPPO_SPECIAL_BUILD |
| static int aee_kpd_enable = 1; |
| #else |
| static int aee_kpd_enable = 0; |
| #endif |
| static void kpd_aee_handler(u32 keycode, u16 pressed); |
| static inline void kpd_update_aee_state(void); |
| #ifdef CONFIG_MACH_MT6785 |
| /* Yonghai.Wang@BSP.TP.Misc 2020/7/17 modified for HW reset distinguish 1878 1879 and 1877. */ |
| int g_cphy_dphy_gpio_value = -1; |
| #endif |
| #define VOLKEYPASSWORD 17331 //0x43b3 |
| int door_open = 0; |
| static unsigned int vol_key_password = 0; |
| static unsigned long start_timer_last = 0; |
| static void kpd_set_vol_key_state(int key, int key_val) |
| { |
| unsigned long start_timer_current = jiffies; |
| |
| if (key == KEY_VOLUMEUP && key_val) |
| vol_key_password = (vol_key_password << 1)|0x01; |
| |
| if(key == KEY_VOLUMEDOWN && key_val) |
| vol_key_password = (vol_key_password << 1)&~0x01; |
| |
| if (key_val) { |
| if (door_open) { |
| door_open = 0; |
| vol_key_password = 0; |
| pr_err("vol_Key_password door_close \n"); |
| } |
| start_timer_current = jiffies; |
| if(start_timer_last != 0){ |
| if (time_after(start_timer_current,start_timer_last + msecs_to_jiffies(1000))){ |
| vol_key_password = 0; |
| } |
| |
| if((VOLKEYPASSWORD == vol_key_password) && (door_open == 0)) |
| { |
| pr_err("vol_key_password door_open \n"); |
| door_open = 1; |
| } |
| } |
| start_timer_last = start_timer_current; |
| } |
| |
| } |
| |
| static void kpd_volumeup_task_process(unsigned long data) |
| { |
| if(vol_key_info.vol_up_val != |
| gpio_get_value(vol_key_info.vol_up_gpio)) { |
| enable_irq(vol_key_info.vol_up_irq); |
| return; |
| } |
| |
| input_report_key(kpd_input_dev, KEY_VOLUMEUP, !vol_key_info.vol_up_val); |
| input_sync(kpd_input_dev); |
| kpd_set_vol_key_state(KEY_VOLUMEUP, vol_key_info.vol_up_val); |
| enable_irq(vol_key_info.vol_up_irq); |
| |
| if (aee_kpd_enable) { |
| kpd_aee_handler(KEY_VOLUMEUP, !vol_key_info.vol_up_val); |
| } |
| } |
| |
| static irqreturn_t kpd_volumeup_irq_handler(int irq, void *dev_id) |
| { |
| disable_irq_nosync(vol_key_info.vol_up_irq); |
| |
| vol_key_info.vol_up_val = gpio_get_value(vol_key_info.vol_up_gpio); |
| if (vol_key_info.vol_up_val) { |
| irq_set_irq_type(vol_key_info.vol_up_irq, IRQ_TYPE_EDGE_FALLING); |
| vol_key_info.vol_up_irq_type = IRQ_TYPE_EDGE_FALLING; |
| } else { |
| irq_set_irq_type(vol_key_info.vol_up_irq, IRQ_TYPE_EDGE_RISING); |
| vol_key_info.vol_up_irq_type = IRQ_TYPE_EDGE_RISING; |
| } |
| |
| if (vol_key_info.vol_up_delay) |
| mod_timer(&vol_key_info.vol_up_timer, |
| jiffies + msecs_to_jiffies(vol_key_info.vol_up_delay)); |
| return IRQ_HANDLED; |
| } |
| |
| static void kpd_volumedown_task_process(unsigned long data) |
| { |
| if(vol_key_info.vol_down_val != |
| gpio_get_value(vol_key_info.vol_down_gpio)) { |
| enable_irq(vol_key_info.vol_down_irq); |
| return; |
| } |
| |
| input_report_key(kpd_input_dev, KEY_VOLUMEDOWN, !vol_key_info.vol_down_val); |
| input_sync(kpd_input_dev); |
| kpd_set_vol_key_state(KEY_VOLUMEDOWN, vol_key_info.vol_down_val); |
| enable_irq(vol_key_info.vol_down_irq); |
| |
| if (aee_kpd_enable) { |
| kpd_aee_handler(KEY_VOLUMEDOWN, !vol_key_info.vol_down_val); |
| } |
| } |
| |
| static irqreturn_t kpd_volumedown_irq_handler(int irq, void *dev_id) |
| { |
| |
| disable_irq_nosync(vol_key_info.vol_down_irq); |
| vol_key_info.vol_down_val = gpio_get_value(vol_key_info.vol_down_gpio); |
| |
| if (vol_key_info.vol_down_val) { |
| irq_set_irq_type(vol_key_info.vol_down_irq, IRQ_TYPE_EDGE_FALLING); |
| vol_key_info.vol_down_irq_type = IRQ_TYPE_EDGE_FALLING; |
| } else { |
| irq_set_irq_type(vol_key_info.vol_down_irq, IRQ_TYPE_EDGE_RISING); |
| vol_key_info.vol_down_irq_type = IRQ_TYPE_EDGE_RISING; |
| } |
| |
| if (vol_key_info.vol_down_delay) |
| mod_timer(&vol_key_info.vol_down_timer, |
| jiffies + msecs_to_jiffies(vol_key_info.vol_down_delay)); |
| return IRQ_HANDLED; |
| } |
| |
| int kpd_klog_en; |
| void __iomem *kp_base; |
| static unsigned int kp_irqnr; |
| struct input_dev *kpd_input_dev; |
| static struct dentry *kpd_droot; |
| static struct dentry *kpd_dklog; |
| unsigned long call_status; |
| static bool kpd_suspend; |
| static unsigned int kp_irqnr; |
| static u32 kpd_keymap[KPD_NUM_KEYS]; |
| static u16 kpd_keymap_state[KPD_NUM_MEMS]; |
| |
| struct input_dev *kpd_input_dev; |
| #ifdef CONFIG_PM_WAKELOCKS |
| struct wakeup_source kpd_suspend_lock; |
| #else |
| struct wake_lock kpd_suspend_lock; |
| #endif |
| struct keypad_dts_data kpd_dts_data; |
| |
| /* for keymap handling */ |
| static void kpd_keymap_handler(unsigned long data); |
| static DECLARE_TASKLET(kpd_keymap_tasklet, kpd_keymap_handler, 0); |
| |
| static void kpd_memory_setting(void); |
| static int kpd_pdrv_probe(struct platform_device *pdev); |
| static int kpd_pdrv_suspend(struct platform_device *pdev, pm_message_t state); |
| static int kpd_pdrv_resume(struct platform_device *pdev); |
| static struct platform_driver kpd_pdrv; |
| |
| static void kpd_memory_setting(void) |
| { |
| kpd_init_keymap(kpd_keymap); |
| kpd_init_keymap_state(kpd_keymap_state); |
| } |
| |
| #if 1 |
| static ssize_t kpd_call_state_store(struct device_driver *ddri, |
| const char *buf, size_t count) |
| { |
| int ret; |
| |
| ret = kstrtoul(buf, 10, &call_status); |
| if (ret) { |
| kpd_print("kpd call state: Invalid values\n"); |
| return -EINVAL; |
| } |
| |
| switch (call_status) { |
| case 1: |
| kpd_print("kpd call state: Idle state!\n"); |
| break; |
| case 2: |
| kpd_print("kpd call state: ringing state!\n"); |
| break; |
| case 3: |
| kpd_print("kpd call state: active or hold state!\n"); |
| break; |
| |
| default: |
| kpd_print("kpd call state: Invalid values\n"); |
| break; |
| } |
| return count; |
| } |
| |
| static ssize_t kpd_call_state_show(struct device_driver *ddri, char *buf) |
| { |
| ssize_t res; |
| |
| res = snprintf(buf, PAGE_SIZE, "%ld\n", call_status); |
| return res; |
| } |
| |
| static DRIVER_ATTR_RW(kpd_call_state); |
| static struct driver_attribute *kpd_attr_list[] = { |
| &driver_attr_kpd_call_state, |
| }; |
| |
| |
| static int kpd_create_attr(struct device_driver *driver) |
| { |
| int idx, err = 0; |
| int num = ARRAY_SIZE(kpd_attr_list); |
| |
| if (driver == NULL) |
| return -EINVAL; |
| |
| for (idx = 0; idx < num; idx++) { |
| err = driver_create_file(driver, kpd_attr_list[idx]); |
| if (err) { |
| kpd_info("driver_create_file (%s) = %d\n", |
| kpd_attr_list[idx]->attr.name, err); |
| break; |
| } |
| } |
| return err; |
| } |
| |
| static int kpd_delete_attr(struct device_driver *driver) |
| { |
| int idx, err = 0; |
| int num = ARRAY_SIZE(kpd_attr_list); |
| |
| if (!driver) |
| return -EINVAL; |
| |
| for (idx = 0; idx < num; idx++) |
| driver_remove_file(driver, kpd_attr_list[idx]); |
| |
| return err; |
| } |
| #endif |
| /*----------------------------------------------------------------------------*/ |
| /* for autotest */ |
| #if KPD_AUTOTEST |
| static const u16 kpd_auto_keymap[] = { |
| KEY_MENU, |
| KEY_HOME, KEY_BACK, |
| KEY_CALL, KEY_ENDCALL, |
| KEY_VOLUMEUP, KEY_VOLUMEDOWN, |
| KEY_FOCUS, KEY_CAMERA, |
| }; |
| #endif |
| /* for AEE manual dump */ |
| #define AEE_VOLUMEUP_BIT 0 |
| #define AEE_VOLUMEDOWN_BIT 1 |
| #define AEE_DELAY_TIME 15 |
| /* enable volup + voldown was pressed 5~15 s Trigger aee manual dump */ |
| #define AEE_ENABLE_5_15 1 |
| static struct hrtimer aee_timer; |
| static unsigned long aee_pressed_keys; |
| static bool aee_timer_started; |
| |
| #if AEE_ENABLE_5_15 |
| #define AEE_DELAY_TIME_5S 5 |
| static struct hrtimer aee_timer_5s; |
| static bool aee_timer_5s_started; |
| static bool flags_5s; |
| #endif |
| static inline void kpd_update_aee_state(void) |
| { |
| if (aee_pressed_keys == ((1 << AEE_VOLUMEUP_BIT) | (1 << AEE_VOLUMEDOWN_BIT))) { |
| /* if volumeup and volumedown was pressed the same time then start the time of ten seconds */ |
| aee_timer_started = true; |
| |
| #if AEE_ENABLE_5_15 |
| aee_timer_5s_started = true; |
| hrtimer_start(&aee_timer_5s, ktime_set(AEE_DELAY_TIME_5S, 0), HRTIMER_MODE_REL); |
| #endif |
| hrtimer_start(&aee_timer, ktime_set(AEE_DELAY_TIME, 0), HRTIMER_MODE_REL); |
| kpd_print("aee_timer started\n"); |
| } else { |
| /* |
| * hrtimer_cancel - cancel a timer and wait for the handler to finish. |
| * Returns: |
| * 0 when the timer was not active. |
| * 1 when the timer was active. |
| */ |
| if (aee_timer_started) { |
| if (hrtimer_cancel(&aee_timer)) { |
| kpd_print("try to cancel hrtimer\n"); |
| #if AEE_ENABLE_5_15 |
| if (flags_5s) { |
| kpd_print("Pressed Volup + Voldown5s~15s then trigger aee manual dump.\n"); |
| /*ZH CHEN*/ |
| /*aee_kernel_reminding("manual dump", "Trigger Vol Up +Vol Down 5s");*/ |
| } |
| #endif |
| |
| } |
| #if AEE_ENABLE_5_15 |
| flags_5s = false; |
| #endif |
| aee_timer_started = false; |
| kpd_print("aee_timer canceled\n"); |
| } |
| #if AEE_ENABLE_5_15 |
| /* |
| * hrtimer_cancel - cancel a timer and wait for the handler to finish. |
| * Returns: |
| * 0 when the timer was not active. |
| * 1 when the timer was active. |
| */ |
| if (aee_timer_5s_started) { |
| if (hrtimer_cancel(&aee_timer_5s)) |
| kpd_print("try to cancel hrtimer (5s)\n"); |
| aee_timer_5s_started = false; |
| kpd_print("aee_timer canceled (5s)\n"); |
| } |
| #endif |
| } |
| } |
| |
| /*Shouli.Wang@ODM_WT.BSP.CHG 2019/10/22, add for key function*/ |
| static void kpd_aee_handler(u32 keycode, u16 pressed) |
| { |
| if (pressed) { |
| if (keycode == KEY_VOLUMEUP) |
| __set_bit(AEE_VOLUMEUP_BIT, &aee_pressed_keys); |
| else if (keycode == KEY_VOLUMEDOWN) |
| __set_bit(AEE_VOLUMEDOWN_BIT, &aee_pressed_keys); |
| else |
| return; |
| kpd_update_aee_state(); |
| } else { |
| if (keycode == KEY_VOLUMEUP) |
| __clear_bit(AEE_VOLUMEUP_BIT, &aee_pressed_keys); |
| else if (keycode == KEY_VOLUMEDOWN) |
| __clear_bit(AEE_VOLUMEDOWN_BIT, &aee_pressed_keys); |
| else |
| return; |
| kpd_update_aee_state(); |
| } |
| } |
| |
| static enum hrtimer_restart aee_timer_func(struct hrtimer *timer) |
| { |
| /* kpd_info("kpd: vol up+vol down AEE manual dump!\n"); */ |
| /* aee_kernel_reminding("manual dump ", "Triggered by press KEY_VOLUMEUP+KEY_VOLUMEDO |
| WN"); */ |
| /*ZH CHEN*/ |
| /*aee_trigger_kdb();*/ |
| if (aee_kpd_enable) { |
| pr_err("%s call bug for aee manual dump.", __func__); |
| BUG(); |
| } |
| |
| return HRTIMER_NORESTART; |
| } |
| |
| #if AEE_ENABLE_5_15 |
| static enum hrtimer_restart aee_timer_5s_func(struct hrtimer *timer) |
| { |
| |
| /* kpd_info("kpd: vol up+vol down AEE manual dump timer 5s !\n"); */ |
| flags_5s = true; |
| return HRTIMER_NORESTART; |
| } |
| #endif |
| /****************************************/ |
| #ifdef CONFIG_LONG_PRESS_MODE_EN |
| void vol_down_long_press(unsigned long pressed) |
| { |
| atomic_set(&vol_down_long_press_flag, 1); |
| } |
| #endif |
| /*****************************************/ |
| |
| #ifdef CONFIG_KPD_PWRKEY_USE_PMIC |
| void kpd_pwrkey_pmic_handler(unsigned long pressed) |
| { |
| kpd_print("Power Key generate, pressed=%ld\n", pressed); |
| if (!kpd_input_dev) { |
| kpd_print("KPD input device not ready\n"); |
| return; |
| } |
| kpd_pmic_pwrkey_hal(pressed); |
| } |
| #endif |
| |
| void kpd_pmic_rstkey_handler(unsigned long pressed) |
| { |
| kpd_print("PMIC reset Key generate, pressed=%ld\n", pressed); |
| if (!kpd_input_dev) { |
| kpd_print("KPD input device not ready\n"); |
| return; |
| } |
| kpd_pmic_rstkey_hal(pressed); |
| #ifdef KPD_PMIC_RSTKEY_MAP |
| kpd_aee_handler(KPD_PMIC_RSTKEY_MAP, pressed); |
| #endif |
| |
| //#ifdef OPLUS_FEATURE_TP_BASIC |
| /* Fuchun.Liao@BSP.CHG.Basic 2018/03/04 modify for enter dump */ |
| if(vol_key_info.homekey_as_vol_up) { |
| kpd_set_vol_key_state(kpd_dts_data.kpd_sw_rstkey, !pressed); |
| |
| if (aee_kpd_enable) { |
| kpd_aee_handler(kpd_dts_data.kpd_sw_rstkey, pressed); |
| } |
| } |
| //#endif /* OPLUS_FEATURE_TP_BASIC */ |
| } |
| |
| static void kpd_keymap_handler(unsigned long data) |
| { |
| u16 i, j; |
| int32_t pressed; |
| u16 new_state[KPD_NUM_MEMS], change, mask; |
| u16 hw_keycode, linux_keycode; |
| void *dest; |
| |
| kpd_get_keymap_state(new_state); |
| #ifdef CONFIG_PM_WAKELOCKS |
| __pm_wakeup_event(&kpd_suspend_lock, 500); |
| #else |
| wake_lock_timeout(&kpd_suspend_lock, HZ / 2); |
| #endif |
| for (i = 0; i < KPD_NUM_MEMS; i++) { |
| change = new_state[i] ^ kpd_keymap_state[i]; |
| if (change == 0U) |
| continue; |
| |
| for (j = 0; j < 16U; j++) { |
| mask = (u16) 1 << j; |
| if ((change & mask) == 0U) |
| continue; |
| |
| hw_keycode = (i << 4) + j; |
| |
| if (hw_keycode >= KPD_NUM_KEYS) |
| continue; |
| |
| /* bit is 1: not pressed, 0: pressed */ |
| pressed = ((new_state[i] & mask) == 0U) ? 1 : 0; |
| kpd_print("(%s) HW keycode = %d\n", |
| (pressed == 1) ? "pressed" : "released", |
| hw_keycode); |
| |
| linux_keycode = kpd_keymap[hw_keycode]; |
| if (linux_keycode == 0U) |
| continue; |
| input_report_key(kpd_input_dev, linux_keycode, pressed); |
| input_sync(kpd_input_dev); |
| kpd_print("report Linux keycode = %d\n", linux_keycode); |
| |
| #ifdef CONFIG_LONG_PRESS_MODE_EN |
| if (pressed) { |
| init_timer(&Long_press_key_timer); |
| Long_press_key_timer.expires = jiffies + 5*HZ; |
| Long_press_key_timer.data = |
| (unsigned long)pressed; |
| Long_press_key_timer.function = |
| vol_down_long_press; |
| add_timer(&Long_press_key_timer); |
| } else { |
| del_timer_sync(&Long_press_key_timer); |
| } |
| if (!pressed && |
| atomic_read(&vol_down_long_press_flag)) { |
| atomic_set(&vol_down_long_press_flag, 0); |
| } |
| #endif |
| } |
| } |
| |
| dest = memcpy(kpd_keymap_state, new_state, sizeof(new_state)); |
| enable_irq(kp_irqnr); |
| } |
| |
| static irqreturn_t kpd_irq_handler(int irq, void *dev_id) |
| { |
| //#ifdef OPLUS_FEATURE_TP_BASIC |
| /*xiongxing.@BSP.Kernel.Driver, 2019/01/26, Add for disable kpd irq handler*/ |
| /* yulianghan@bootloader.bootflow 2020/10/30, disable kpd irq handler for 20682*/ |
| if(get_project() == 20682 || get_project() == 19661) |
| return IRQ_HANDLED; //tanyang test |
| //#endif /*OPLUS_FEATURE_TP_BASIC*/ |
| /* use _nosync to avoid deadlock */ |
| disable_irq_nosync(kp_irqnr); |
| tasklet_schedule(&kpd_keymap_tasklet); |
| return IRQ_HANDLED; |
| } |
| |
| static int kpd_open(struct input_dev *dev) |
| { |
| /* void __user *uarg = (void __user *)arg; */ |
| return 0; |
| } |
| |
| void kpd_get_dts_info(struct device_node *node) |
| { |
| int32_t ret; |
| |
| of_property_read_u32(node, "mediatek,kpd-key-debounce", |
| &kpd_dts_data.kpd_key_debounce); |
| of_property_read_u32(node, "mediatek,kpd-sw-pwrkey", |
| &kpd_dts_data.kpd_sw_pwrkey); |
| of_property_read_u32(node, "mediatek,kpd-hw-pwrkey", |
| &kpd_dts_data.kpd_hw_pwrkey); |
| of_property_read_u32(node, "mediatek,kpd-sw-rstkey", |
| &kpd_dts_data.kpd_sw_rstkey); |
| of_property_read_u32(node, "mediatek,kpd-hw-rstkey", |
| &kpd_dts_data.kpd_hw_rstkey); |
| of_property_read_u32(node, "mediatek,kpd-use-extend-type", |
| &kpd_dts_data.kpd_use_extend_type); |
| of_property_read_u32(node, "mediatek,kpd-hw-dl-key1", |
| &kpd_dts_data.kpd_hw_dl_key1); |
| of_property_read_u32(node, "mediatek,kpd-hw-dl-key2", |
| &kpd_dts_data.kpd_hw_dl_key2); |
| of_property_read_u32(node, "mediatek,kpd-hw-dl-key3", |
| &kpd_dts_data.kpd_hw_dl_key3); |
| of_property_read_u32(node, "mediatek,kpd-hw-recovery-key", |
| &kpd_dts_data.kpd_hw_recovery_key); |
| of_property_read_u32(node, "mediatek,kpd-hw-factory-key", |
| &kpd_dts_data.kpd_hw_factory_key); |
| of_property_read_u32(node, "mediatek,kpd-hw-map-num", |
| &kpd_dts_data.kpd_hw_map_num); |
| ret = of_property_read_u32_array(node, "mediatek,kpd-hw-init-map", |
| kpd_dts_data.kpd_hw_init_map, |
| kpd_dts_data.kpd_hw_map_num); |
| |
| if (ret) { |
| kpd_print("kpd-hw-init-map was not defined in dts.\n"); |
| memset(kpd_dts_data.kpd_hw_init_map, 0, |
| sizeof(kpd_dts_data.kpd_hw_init_map)); |
| } |
| |
| kpd_print("deb= %d, sw-pwr= %d, hw-pwr= %d, hw-rst= %d, sw-rst= %d\n", |
| kpd_dts_data.kpd_key_debounce, kpd_dts_data.kpd_sw_pwrkey, |
| kpd_dts_data.kpd_hw_pwrkey, kpd_dts_data.kpd_hw_rstkey, |
| kpd_dts_data.kpd_sw_rstkey); |
| } |
| |
| //#ifdef OPLUS_FEATURE_TP_BASIC |
| /* Bin.Li@EXP.BSP.bootloader.bootflow, 2017/05/15, Add for keypad volume up and volume down */ |
| static int kpd_request_named_gpio(struct vol_info *kpd, |
| const char *label, int *gpio) |
| { |
| struct device *dev = kpd->dev; |
| struct device_node *np = dev->of_node; |
| int rc = of_get_named_gpio(np, label, 0); |
| if (rc < 0) { |
| dev_err(dev, "failed to get '%s'\n", label); |
| return rc; |
| } |
| |
| *gpio = rc; |
| rc = devm_gpio_request(dev, *gpio, label); |
| if (rc) { |
| dev_err(dev, "failed to request gpio %d\n", *gpio); |
| return rc; |
| } |
| |
| //dev_info(dev, "%s - gpio: %d\n", label, *gpio); |
| return 0; |
| } |
| static int init_custom_gpio_state(struct platform_device *client) { |
| struct pinctrl *pinctrl1; |
| struct pinctrl_state *volume_up_as_int, *volume_down_as_int; |
| struct device_node *node = NULL; |
| u32 intr[4] = {0}; |
| int ret; |
| u32 debounce_time = 0; |
| |
| pinctrl1 = devm_pinctrl_get(&client->dev); |
| if (IS_ERR(pinctrl1)) { |
| ret = PTR_ERR(pinctrl1); |
| kpd_print("can not find keypad pintrl1"); |
| return ret; |
| } |
| |
| /*for key volume up*/ |
| if (!vol_key_info.homekey_as_vol_up) { |
| volume_up_as_int = pinctrl_lookup_state(pinctrl1, "volume_up_as_int"); |
| if (IS_ERR(volume_up_as_int)) { |
| ret = PTR_ERR(volume_up_as_int); |
| kpd_print("can not find gpio of volume up\n"); |
| return ret; |
| } else { |
| ret = pinctrl_select_state(pinctrl1, volume_up_as_int); |
| if (ret < 0){ |
| kpd_print("error to set gpio state\n"); |
| return ret; |
| } |
| |
| node = of_find_compatible_node(NULL, NULL, "mediatek, VOLUME_UP-eint"); |
| if (node) { |
| of_property_read_u32_array(node , "interrupts", intr, ARRAY_SIZE(intr)); |
| pr_info("volume up intr[0-3] = %d %d %d %d\r\n", intr[0] ,intr[1], intr[2] ,intr[3]); |
| //vol_key_info.vol_up_gpio = intr[0]; |
| vol_key_info.vol_up_irq = irq_of_parse_and_map(node, 0); |
| ret = of_property_read_u32(node, "debounce", &debounce_time); |
| if (ret) { |
| pr_err("%s get debounce_time fail\n", __func__); |
| } |
| pr_err("%s debounce_time:%d\n", __func__, debounce_time); |
| } else { |
| pr_err("%d volume up irp node not exist\n", __LINE__); |
| return -1; |
| } |
| vol_key_info.vol_up_irq_type = IRQ_TYPE_EDGE_FALLING; |
| ret = request_irq(vol_key_info.vol_up_irq, (irq_handler_t)kpd_volumeup_irq_handler, IRQF_TRIGGER_FALLING, KPD_VOL_UP_NAME, NULL); |
| if(ret){ |
| pr_err("%d request irq failed\n", __LINE__); |
| return -1; |
| } |
| if (vol_key_info.vol_up_gpio > 0 && debounce_time) { |
| vol_key_info.vol_up_delay = debounce_time / 1000; |
| setup_timer(&vol_key_info.vol_up_timer, |
| kpd_volumeup_task_process, 0); |
| } |
| } |
| } |
| |
| /*for key of volume down*/ |
| volume_down_as_int = pinctrl_lookup_state(pinctrl1, "volume_down_as_int"); |
| if (IS_ERR(volume_down_as_int)) { |
| ret = PTR_ERR(volume_down_as_int); |
| kpd_print("can not find gpio of volume down\n"); |
| return ret; |
| } else { |
| ret = pinctrl_select_state(pinctrl1, volume_down_as_int); |
| if (ret < 0){ |
| kpd_print("error to set gpio state\n"); |
| return ret; |
| } |
| |
| node = of_find_compatible_node(NULL, NULL, "mediatek, VOLUME_DOWN-eint"); |
| if (node) { |
| of_property_read_u32_array(node , "interrupts", intr, ARRAY_SIZE(intr)); |
| pr_info("volume down intr[0-3] = %d %d %d %d\r\n", intr[0] ,intr[1], intr[2], intr[3]); |
| //vol_key_info.vol_down_gpio = intr[0]; |
| vol_key_info.vol_down_irq = irq_of_parse_and_map(node, 0); |
| } else { |
| pr_err("%d volume down irp node not exist\n", __LINE__); |
| return -1; |
| } |
| ret = of_property_read_u32(node, "debounce", &debounce_time); |
| if (ret) { |
| pr_err("%s vol_down get debounce_time fail\n", __func__); |
| } |
| pr_err("%s vol_down debounce_time:%d\n", __func__, debounce_time); |
| vol_key_info.vol_down_irq_type = IRQ_TYPE_EDGE_FALLING; |
| ret = request_irq(vol_key_info.vol_down_irq, (irq_handler_t)kpd_volumedown_irq_handler, IRQF_TRIGGER_FALLING, KPD_VOL_DOWN_NAME, NULL); |
| if(ret){ |
| pr_err("%d request irq failed\n", __LINE__); |
| return -1; |
| } |
| if (vol_key_info.vol_down_gpio > 0 && debounce_time) { |
| vol_key_info.vol_down_delay = debounce_time / 1000; |
| setup_timer(&vol_key_info.vol_down_timer, |
| kpd_volumedown_task_process, 0); |
| } |
| } |
| |
| kpd_print(" init_custom_gpio_state End\n"); |
| return 0; |
| |
| } |
| //#endif /*OPLUS_FEATURE_TP_BASIC*/ |
| //#ifdef OPLUS_FEATURE_TP_BASIC |
| /* Fuchun.Liao@BSP.CHG.Basic 2018/01/09 modify for aee_kpd_enable */ |
| static ssize_t aee_kpd_enable_read(struct file *filp, char __user *buff, |
| size_t count, loff_t *off) |
| { |
| char page[256] = {0}; |
| char read_data[16] = {0}; |
| int len = 0; |
| |
| if (aee_kpd_enable) |
| read_data[0] = '1'; |
| else |
| read_data[0] = '0'; |
| |
| len = sprintf(page, "%s", read_data); |
| |
| if(len > *off) |
| len -= *off; |
| else |
| len = 0; |
| if (copy_to_user(buff, page, (len < count ? len : count))) { |
| return -EFAULT; |
| } |
| *off += len < count ? len : count; |
| return (len < count ? len : count); |
| } |
| |
| static ssize_t aee_kpd_enable_write(struct file *filp, const char __user *buff, |
| size_t len, loff_t *data) |
| { |
| char temp[16] = {0}; |
| |
| if (copy_from_user(temp, buff, len)) { |
| pr_err("aee_kpd_enable_write error.\n"); |
| return -EFAULT; |
| } |
| sscanf(temp, "%d", &aee_kpd_enable); |
| pr_err("%s enable:%d\n", __func__, aee_kpd_enable); |
| |
| return len; |
| } |
| |
| static const struct file_operations aee_kpd_enable_proc_fops = { |
| .write = aee_kpd_enable_write, |
| .read = aee_kpd_enable_read, |
| }; |
| static void init_proc_aee_kpd_enable(void) |
| { |
| struct proc_dir_entry *p = NULL; |
| |
| p = proc_create("aee_kpd_enable", 0664, |
| NULL, &aee_kpd_enable_proc_fops); |
| if (!p) |
| pr_err("proc_create aee_kpd_enable ops fail!\n"); |
| |
| } |
| //#endif /* OPLUS_FEATURE_TP_BASIC */ |
| static int32_t kpd_gpio_init(struct device *dev) |
| { |
| struct pinctrl *keypad_pinctrl; |
| struct pinctrl_state *kpd_default; |
| int32_t ret; |
| |
| if (dev == NULL) { |
| kpd_print("kpd device is NULL!\n"); |
| ret = -1; |
| } else { |
| keypad_pinctrl = devm_pinctrl_get(dev); |
| if (IS_ERR(keypad_pinctrl)) { |
| ret = -1; |
| kpd_print("Cannot find keypad_pinctrl!\n"); |
| } else { |
| kpd_default = pinctrl_lookup_state(keypad_pinctrl, |
| "default"); |
| if (IS_ERR(kpd_default)) { |
| ret = -1; |
| kpd_print("Cannot find ecall_state!\n"); |
| } else |
| ret = pinctrl_select_state(keypad_pinctrl, |
| kpd_default); |
| } |
| } |
| return ret; |
| } |
| |
| static int mt_kpd_debugfs(void) |
| { |
| #ifdef CONFIG_MTK_ENG_BUILD |
| kpd_klog_en = 1; |
| #else |
| kpd_klog_en = 0; |
| #endif |
| kpd_droot = debugfs_create_dir("keypad", NULL); |
| if (IS_ERR_OR_NULL(kpd_droot)) |
| return PTR_ERR(kpd_droot); |
| |
| kpd_dklog = debugfs_create_u32("debug", 0600, kpd_droot, &kpd_klog_en); |
| |
| return 0; |
| } |
| |
| static int kpd_pdrv_probe(struct platform_device *pdev) |
| { |
| struct clk *kpd_clk = NULL; |
| u32 i; |
| int32_t err = 0; |
| //#ifdef OPLUS_FEATURE_TP_BASIC |
| /* Bin.Li@EXP.BSP.bootloader.bootflow, 2017/05/15, Add for keypad volume up and volume down */ |
| struct device *dev = &pdev->dev; |
| struct vol_info *kpd_oppo; |
| |
| kpd_oppo = devm_kzalloc(dev, sizeof(*kpd_oppo), GFP_KERNEL); |
| //#endif /*OPLUS_FEATURE_TP_BASIC*/ |
| if (!pdev->dev.of_node) { |
| kpd_notice("no kpd dev node\n"); |
| return -ENODEV; |
| } |
| |
| kpd_clk = devm_clk_get(&pdev->dev, "kpd-clk"); |
| if (!IS_ERR(kpd_clk)) { |
| err = clk_prepare_enable(kpd_clk); |
| if (err) |
| kpd_notice("get kpd-clk fail: %d\n", err); |
| } else { |
| kpd_notice("kpd-clk is default set by ccf.\n"); |
| } |
| |
| kp_base = of_iomap(pdev->dev.of_node, 0); |
| if (!kp_base) { |
| kpd_notice("KP iomap failed\n"); |
| return -ENODEV; |
| }; |
| |
| kp_irqnr = irq_of_parse_and_map(pdev->dev.of_node, 0); |
| if (!kp_irqnr) { |
| kpd_notice("KP get irqnr failed\n"); |
| return -ENODEV; |
| } |
| kpd_info("kp base: 0x%p, addr:0x%p, kp irq: %d\n", |
| kp_base, &kp_base, kp_irqnr); |
| err = kpd_gpio_init(&pdev->dev); |
| if (err != 0) |
| kpd_print("gpio init failed\n"); |
| |
| kpd_get_dts_info(pdev->dev.of_node); |
| |
| kpd_memory_setting(); |
| |
| kpd_input_dev = devm_input_allocate_device(&pdev->dev); |
| if (!kpd_input_dev) { |
| kpd_notice("input allocate device fail.\n"); |
| return -ENOMEM; |
| } |
| |
| kpd_input_dev->name = KPD_NAME; |
| kpd_input_dev->id.bustype = BUS_HOST; |
| kpd_input_dev->id.vendor = 0x2454; |
| kpd_input_dev->id.product = 0x6500; |
| kpd_input_dev->id.version = 0x0010; |
| kpd_input_dev->open = kpd_open; |
| kpd_input_dev->dev.parent = &pdev->dev; |
| |
| __set_bit(EV_KEY, kpd_input_dev->evbit); |
| #if defined(CONFIG_KPD_PWRKEY_USE_PMIC) |
| __set_bit(kpd_dts_data.kpd_sw_pwrkey, kpd_input_dev->keybit); |
| kpd_keymap[8] = 0; |
| #endif |
| if (!kpd_dts_data.kpd_use_extend_type) { |
| for (i = 17; i < KPD_NUM_KEYS; i += 9) |
| 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); |
| } |
| |
| if (kpd_dts_data.kpd_sw_rstkey) |
| __set_bit(kpd_dts_data.kpd_sw_rstkey, kpd_input_dev->keybit); |
| #ifdef KPD_KEY_MAP |
| __set_bit(KPD_KEY_MAP, kpd_input_dev->keybit); |
| #endif |
| #ifdef CONFIG_MTK_MRDUMP_KEY |
| __set_bit(KEY_RESTART, kpd_input_dev->keybit); |
| #endif |
| |
| err = input_register_device(kpd_input_dev); |
| if (err) { |
| kpd_notice("register input device failed (%d)\n", err); |
| return err; |
| } |
| #ifdef CONFIG_PM_WAKELOCKS |
| wakeup_source_init(&kpd_suspend_lock, "kpd wakelock"); |
| #else |
| wake_lock_init(&kpd_suspend_lock, WAKE_LOCK_SUSPEND, "kpd wakelock"); |
| #endif |
| /* register IRQ and EINT */ |
| kpd_set_debounce(kpd_dts_data.kpd_key_debounce); |
| |
| err = request_irq(kp_irqnr, kpd_irq_handler, IRQF_TRIGGER_NONE, |
| KPD_NAME, NULL); |
| if (err) { |
| kpd_notice("register IRQ failed (%d)\n", err); |
| input_unregister_device(kpd_input_dev); |
| return err; |
| } |
| |
| #ifdef CONFIG_MTK_MRDUMP_KEY |
| mt_eint_register(); |
| #endif |
| #ifdef CONFIG_MTK_PMIC_NEW_ARCH |
| long_press_reboot_function_setting(); |
| #endif |
| hrtimer_init(&aee_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); |
| aee_timer.function = aee_timer_func; |
| |
| #if AEE_ENABLE_5_15 |
| hrtimer_init(&aee_timer_5s, CLOCK_MONOTONIC, HRTIMER_MODE_REL); |
| aee_timer_5s.function = aee_timer_5s_func; |
| #endif |
| err = kpd_create_attr(&kpd_pdrv.driver); |
| if (err) { |
| kpd_notice("create attr file fail\n"); |
| kpd_delete_attr(&kpd_pdrv.driver); |
| return err; |
| } |
| //#ifdef OPLUS_FEATURE_TP_BASIC |
| /* Bin.Li@EXP.BSP.bootloader.bootflow, 2017/05/15, Add for keypad volume up and volume down */ |
| kpd_oppo->dev = dev; |
| dev_set_drvdata(dev, kpd_oppo); |
| kpd_oppo->pdev = pdev; |
| |
| if (kpd_dts_data.kpd_sw_rstkey == KEY_VOLUMEUP) { |
| vol_key_info.homekey_as_vol_up = true; |
| } else { |
| vol_key_info.homekey_as_vol_up = false; |
| } |
| |
| if (!vol_key_info.homekey_as_vol_up) { // means not home key as volume up, defined on dws |
| err = kpd_request_named_gpio(kpd_oppo, "keypad,volume-up", |
| &vol_key_info.vol_up_gpio); |
| |
| if (err) { |
| pr_err("%s lfc request keypad,volume-up fail\n", __func__); |
| return -1; |
| } |
| err = gpio_direction_input(vol_key_info.vol_up_gpio); |
| |
| if (err < 0) { |
| dev_err(&kpd_oppo->pdev->dev, |
| "gpio_direction_input failed for vol_up INT.\n"); |
| return -1; |
| } |
| } |
| |
| err = kpd_request_named_gpio(kpd_oppo, "keypad,volume-down", |
| &vol_key_info.vol_down_gpio); |
| if (err) { |
| pr_err("%s request keypad,volume-down fail\n", __func__); |
| return -1; |
| } |
| err = gpio_direction_input(vol_key_info.vol_down_gpio); |
| |
| if (err < 0) { |
| dev_err(&kpd_oppo->pdev->dev, |
| "gpio_direction_input failed for vol_down INT.\n"); |
| return -1; |
| } |
| |
| if (init_custom_gpio_state(pdev) < 0) { |
| pr_err("init gpio state failed\n"); |
| return -1; |
| } |
| |
| //disable keypad scan function |
| kpd_wakeup_src_setting(0); |
| |
| //enable_irq(vol_key_info.vol_up_irq); |
| vol_key_info.vol_up_irq_enabled = 1; |
| //enable_irq(vol_key_info.vol_down_irq); |
| vol_key_info.vol_down_irq_enabled = 1; |
| |
| __set_bit(KEY_VOLUMEDOWN, kpd_input_dev->keybit); |
| __set_bit(KEY_VOLUMEUP, kpd_input_dev->keybit); |
| __set_bit(KEY_POWER, kpd_input_dev->keybit); |
| //#endif /*OPLUS_FEATURE_TP_BASIC*/ |
| //#ifdef OPLUS_FEATURE_TP_BASIC |
| /* Fuchun.Liao@BSP.CHG.Basic 2018/01/09 modify for aee_kpd_enable */ |
| init_proc_aee_kpd_enable(); |
| //#endif /* OPLUS_FEATURE_TP_BASIC */ |
| /* Add kpd debug node */ |
| mt_kpd_debugfs(); |
| |
| kpd_info("kpd_probe OK.\n"); |
| |
| return err; |
| } |
| |
| static int kpd_pdrv_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| kpd_suspend = true; |
| //#ifndef OPLUS_FEATURE_TP_BASIC |
| /* Bin.Li@EXP.BSP.bootloader.bootflow, 2017/05/15, Remove for we use seperated interrupts for volume up and down */ |
| #ifdef MTK_KP_WAKESOURCE |
| if (call_status == 2) { |
| kpd_print("kpd_early_suspend wake up source enable!! (%d)\n", |
| kpd_suspend); |
| } else { |
| kpd_wakeup_src_setting(0); |
| kpd_print("kpd_early_suspend wake up source disable!! (%d)\n", |
| kpd_suspend); |
| } |
| #endif |
| //#endif /*OPLUS_FEATURE_TP_BASIC*/ |
| kpd_print("suspend!! (%d)\n", kpd_suspend); |
| return 0; |
| } |
| |
| static int kpd_pdrv_resume(struct platform_device *pdev) |
| { |
| kpd_suspend = false; |
| //#ifndef OPLUS_FEATURE_TP_BASIC |
| /* Bin.Li@EXP.BSP.bootloader.bootflow, 2017/05/15, Remove for we use seperated interrupts for volume up and down */ |
| #ifdef MTK_KP_WAKESOURCE |
| if (call_status == 2) { |
| kpd_print("kpd_early_suspend wake up source enable!! (%d)\n", |
| kpd_suspend); |
| } else { |
| kpd_print("kpd_early_suspend wake up source resume!! (%d)\n", |
| kpd_suspend); |
| kpd_wakeup_src_setting(1); |
| } |
| #endif |
| //#endif /*OPLUS_FEATURE_TP_BASIC*/ |
| kpd_print("resume!! (%d)\n", kpd_suspend); |
| return 0; |
| } |
| |
| static const struct of_device_id kpd_of_match[] = { |
| {.compatible = "mediatek,mt8167-keypad"}, |
| {.compatible = "mediatek,kp"}, |
| {}, |
| }; |
| |
| static struct platform_driver kpd_pdrv = { |
| .probe = kpd_pdrv_probe, |
| .suspend = kpd_pdrv_suspend, |
| .resume = kpd_pdrv_resume, |
| .driver = { |
| .name = KPD_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = kpd_of_match, |
| }, |
| }; |
| |
| module_platform_driver(kpd_pdrv); |
| |
| MODULE_AUTHOR("Mediatek Corporation"); |
| MODULE_DESCRIPTION("MTK Keypad (KPD) Driver"); |
| MODULE_LICENSE("GPL"); |