| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2019 MediaTek Inc. |
| * Author: Argus Lin <argus.lin@mediatek.com> |
| */ |
| |
| #include <linux/of_gpio.h> |
| #include <linux/of.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_device.h> |
| #include <linux/of_address.h> |
| #include <linux/input.h> |
| #include <linux/kthread.h> |
| #include <linux/io.h> |
| #include <linux/sched/clock.h> |
| #include <linux/workqueue.h> |
| #include <linux/timer.h> |
| #include <linux/delay.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/iio/consumer.h> |
| #include <linux/nvmem-consumer.h> |
| #include <linux/pinctrl/consumer.h> |
| #include <linux/init.h> |
| #include <linux/irqdomain.h> |
| #include <linux/irq.h> |
| #include <linux/regmap.h> |
| #include <sound/soc.h> |
| #include <sound/jack.h> |
| #include <linux/mfd/mt6397/core.h> |
| #include "mt6359p-accdet.h" |
| #include "mt6359p.h" |
| /* grobal variable definitions */ |
| #define REGISTER_VAL(x) (x - 1) |
| #define HAS_CAP(_c, _x) (((_c) & (_x)) == (_x)) |
| #define ACCDET_PMIC_EINT_IRQ BIT(0) |
| #define ACCDET_AP_GPIO_EINT BIT(1) |
| |
| #define ACCDET_PMIC_EINT0 BIT(2) |
| #define ACCDET_PMIC_EINT1 BIT(3) |
| #define ACCDET_PMIC_BI_EINT BIT(4) |
| |
| #define ACCDET_PMIC_GPIO_TRIG_EINT BIT(5) |
| #define ACCDET_PMIC_INVERTER_TRIG_EINT BIT(6) |
| #define ACCDET_PMIC_RSV_EINT BIT(7) |
| |
| #define ACCDET_THREE_KEY BIT(8) |
| #define ACCDET_FOUR_KEY BIT(9) |
| #define ACCDET_TRI_KEY_CDD BIT(10) |
| #define ACCDET_RSV_KEY BIT(11) |
| |
| #define ACCDET_ANALOG_FASTDISCHARGE BIT(12) |
| #define ACCDET_DIGITAL_FASTDISCHARGE BIT(13) |
| #define ACCDET_AD_FASTDISCHRAGE BIT(14) |
| |
| #define ACCDET_MOISTURE_DETECTED BIT(15) |
| |
| #define RET_LT_5K (-1) |
| #define RET_GT_5K (0) |
| |
| /* Used to let accdet know if the pin has been fully plugged-in */ |
| #define EINT_PLUG_OUT (0) |
| #define EINT_PLUG_IN (1) |
| #define EINT_MOISTURE_DETECTED (2) |
| |
| struct mt63xx_accdet_data { |
| struct snd_soc_jack jack; |
| struct platform_device *pdev; |
| struct device *dev; |
| struct accdet_priv *data; |
| atomic_t init_once; |
| struct regmap *regmap; |
| struct iio_channel *accdet_auxadc; |
| struct nvmem_device *accdet_efuse; |
| int accdet_irq; |
| int accdet_eint0; |
| int accdet_eint1; |
| struct wakeup_source *wake_lock; |
| struct wakeup_source *timer_lock; |
| struct mutex res_lock; |
| dev_t accdet_devno; |
| struct class *accdet_class; |
| int button_status; |
| bool eint_sync_flag; |
| /* accdet FSM State & lock*/ |
| u32 cur_eint_state; |
| u32 eint0_state; |
| u32 eint1_state; |
| u32 accdet_status; |
| u32 cable_type; |
| u32 cur_key; |
| u32 cali_voltage; |
| int auxadc_offset; |
| u32 eint_id; |
| bool thing_in_flag; |
| /* when caps include ACCDET_AP_GPIO_EINT */ |
| struct pinctrl *pinctrl; |
| struct pinctrl_state *pins_eint; |
| u32 gpiopin; |
| u32 gpio_hp_deb; |
| u32 gpioirq; |
| u32 accdet_eint_type; |
| /* when MICBIAS_DISABLE_TIMER timeout, queue work: dis_micbias_work */ |
| struct work_struct delay_init_work; |
| struct workqueue_struct *delay_init_workqueue; |
| struct work_struct dis_micbias_work; |
| struct workqueue_struct *dis_micbias_workqueue; |
| struct work_struct accdet_work; |
| struct workqueue_struct *accdet_workqueue; |
| /* when eint issued, queue work: eint_work */ |
| struct work_struct eint_work; |
| struct workqueue_struct *eint_workqueue; |
| u32 water_r; |
| u32 moisture_ext_r; |
| u32 moisture_int_r; |
| u32 moisture_vm; |
| u32 moisture_vdd_offset; |
| u32 moisture_offset; |
| u32 moisture_eint_offset; |
| }; |
| static struct mt63xx_accdet_data *accdet; |
| |
| static struct head_dts_data accdet_dts; |
| struct pwm_deb_settings *cust_pwm_deb; |
| |
| struct accdet_priv { |
| u32 caps; |
| }; |
| |
| static struct accdet_priv mt6359_accdet[] = { |
| { |
| .caps = 0, |
| }, |
| }; |
| |
| const struct of_device_id accdet_of_match[] = { |
| { |
| .compatible = "mediatek,mt6359p-accdet", |
| .data = &mt6359_accdet, |
| }, { |
| .compatible = "mediatek,mt8163-accdet", |
| }, { |
| .compatible = "mediatek,mt8173-accdet", |
| }, { |
| /* sentinel */ |
| }, |
| }; |
| |
| static struct platform_driver accdet_driver; |
| static const struct snd_soc_component_driver accdet_soc_driver; |
| |
| static atomic_t accdet_first; |
| #define ACCDET_INIT_WAIT_TIMER (10 * HZ) |
| static struct timer_list accdet_init_timer; |
| static void delay_init_timerhandler(struct timer_list *t); |
| /* micbias_timer: disable micbias if no accdet irq after eint, |
| * timeout: 6 seconds |
| * timerHandler: dis_micbias_timerhandler() |
| */ |
| #define MICBIAS_DISABLE_TIMER (6 * HZ) |
| static struct timer_list micbias_timer; |
| static void dis_micbias_timerhandler(struct timer_list *t); |
| static bool dis_micbias_done; |
| static char accdet_log_buf[1280]; |
| static bool debug_thread_en; |
| static bool dump_reg; |
| static struct task_struct *thread; |
| |
| static u32 button_press_debounce = 0x400; |
| static u32 button_press_debounce_01 = 0x800; |
| |
| /* local function declaration */ |
| static void accdet_init_once(void); |
| static inline void accdet_init(void); |
| static void accdet_init_debounce(void); |
| static void accdet_modify_vref_volt_self(void); |
| static u32 adjust_eint_analog_setting(void); |
| static u32 adjust_moisture_analog_setting(u32 eintID); |
| static u32 adjust_moisture_setting(u32 moistureID, u32 eintID); |
| static u32 adjust_eint_setting(u32 eintsts); |
| static void config_analog_moisture_init_by_mode(void); |
| static void config_digital_init_by_mode(void); |
| static void config_eint_init_by_mode(void); |
| static u32 config_moisture_detect_1_0(void); |
| static u32 config_moisture_detect_1_1(void); |
| static u32 config_moisture_detect_2_1(void); |
| static u32 config_moisture_detect_2_1_1(void); |
| static u32 get_moisture_det_en(void); |
| static u32 get_moisture_sw_auxadc_check(void); |
| static u32 get_triggered_eint(void); |
| static void recover_eint_analog_setting(void); |
| static void recover_eint_digital_setting(void); |
| static void recover_eint_setting(u32 eintsts); |
| static void recover_moisture_setting(u32 moistureID); |
| static void send_status_event(u32 cable_type, u32 status); |
| |
| /* global function declaration */ |
| inline u32 accdet_read(u32 addr) |
| { |
| u32 val = 0; |
| |
| regmap_read(accdet->regmap, addr, &val); |
| return val; |
| } |
| |
| inline u32 accdet_read_bits(u32 addr, u32 shift, u32 mask) |
| { |
| u32 val = 0; |
| |
| val = accdet_read(addr); |
| return ((val>>shift) & mask); |
| } |
| |
| inline void accdet_write(u32 addr, u32 wdata) |
| { |
| regmap_write(accdet->regmap, addr, wdata); |
| } |
| |
| inline void accdet_update_bits(u32 addr, u32 shift, u32 mask, u32 data) |
| { |
| regmap_update_bits(accdet->regmap, addr, mask << shift, |
| data << shift); |
| } |
| |
| inline void accdet_update_bit(u32 addr, u32 shift) |
| { |
| unsigned int mask = shift; |
| |
| regmap_update_bits(accdet->regmap, addr, BIT(mask), |
| BIT(shift)); |
| } |
| |
| inline void accdet_clear_bits(u32 addr, unsigned int shift, |
| unsigned int mask, unsigned int data) |
| { |
| regmap_update_bits(accdet->regmap, addr, mask << shift, 0); |
| } |
| |
| inline void accdet_clear_bit(u32 addr, u32 shift) |
| { |
| unsigned int mask = shift; |
| |
| regmap_update_bits(accdet->regmap, addr, BIT(mask), 0); |
| } |
| |
| static void mini_dump_register(void) |
| { |
| int addr = 0, st_addr = 0, end_addr = 0, idx = 0, log_size = 0; |
| |
| log_size += |
| sprintf(accdet_log_buf, |
| "(0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x", |
| ACCDET_SW_EN_ADDR, |
| accdet_read(ACCDET_SW_EN_ADDR), |
| ACCDET_CMP_PWM_EN_ADDR, |
| accdet_read(ACCDET_CMP_PWM_EN_ADDR), |
| ACCDET_IRQ_ADDR, |
| accdet_read(ACCDET_IRQ_ADDR), |
| ACCDET_DA_STABLE_ADDR, |
| accdet_read(ACCDET_DA_STABLE_ADDR)); |
| log_size += sprintf(accdet_log_buf + log_size, |
| "(0x%x)=0x%x (0x%x)=0x%x\naccdet (0x%x)=0x%x (0x%x)=0x%x", |
| ACCDET_HWMODE_EN_ADDR, |
| accdet_read(ACCDET_HWMODE_EN_ADDR), |
| ACCDET_CMPEN_SEL_ADDR, |
| accdet_read(ACCDET_CMPEN_SEL_ADDR), |
| ACCDET_CMPEN_SW_ADDR, |
| accdet_read(ACCDET_CMPEN_SW_ADDR), |
| AD_AUDACCDETCMPOB_ADDR, |
| accdet_read(AD_AUDACCDETCMPOB_ADDR)); |
| log_size += sprintf(accdet_log_buf + log_size, |
| "(0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x\n", |
| AD_EINT0CMPMOUT_ADDR, |
| accdet_read(AD_EINT0CMPMOUT_ADDR), |
| AD_EINT0INVOUT_ADDR, |
| accdet_read(AD_EINT0INVOUT_ADDR), |
| ACCDET_EN_ADDR, |
| accdet_read(ACCDET_EN_ADDR), |
| AD_AUDACCDETCMPOB_ADDR, |
| accdet_read(AD_AUDACCDETCMPOB_ADDR)); |
| st_addr = RG_AUDPWDBMICBIAS0_ADDR; |
| end_addr = RG_ACCDETSPARE_ADDR; |
| for (addr = st_addr; addr <= end_addr; addr += 8) { |
| idx = addr; |
| log_size += sprintf(accdet_log_buf + log_size, |
| "(0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x\n", |
| idx, accdet_read(idx), |
| idx+2, accdet_read(idx+2), |
| idx+4, accdet_read(idx+4), |
| idx+6, accdet_read(idx+6)); |
| } |
| pr_notice("\naccdet %s %d", accdet_log_buf, log_size); |
| } |
| |
| static void dump_register(void) |
| { |
| int addr = 0, st_addr = 0, end_addr = 0, idx = 0; |
| |
| pr_notice("Accdet EINTx support,MODE_%d regs:\n", accdet_dts.mic_mode); |
| if (dump_reg) { |
| pr_notice("ACCDET_RG\n"); |
| st_addr = ACCDET_AUXADC_SEL_ADDR; |
| end_addr = ACCDET_MON_FLAG_EN_ADDR; |
| for (addr = st_addr; addr <= end_addr; addr += 8) { |
| idx = addr; |
| pr_notice("(0x%x)=0x%x (0x%x)=0x%x ", |
| idx, accdet_read(idx), |
| idx+2, accdet_read(idx+2)); |
| pr_notice("(0x%x)=0x%x (0x%x)=0x%x\n", |
| idx+4, accdet_read(idx+4), |
| idx+6, accdet_read(idx+6)); |
| } |
| pr_notice("AUDDEC_ANA_RG\n"); |
| st_addr = RG_AUDPREAMPLON_ADDR; |
| end_addr = RG_CLKSQ_EN_ADDR; |
| for (addr = st_addr; addr <= end_addr; addr += 8) { |
| idx = addr; |
| pr_notice("(0x%x)=0x%x (0x%x)=0x%x ", |
| idx, accdet_read(idx), |
| idx+2, accdet_read(idx+2)); |
| pr_notice("(0x%x)=0x%x (0x%x)=0x%x\n", |
| idx+4, accdet_read(idx+4), |
| idx+6, accdet_read(idx+6)); |
| } |
| |
| pr_notice("(0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x\n", |
| RG_RTC32K_CK_PDN_ADDR, |
| accdet_read(RG_RTC32K_CK_PDN_ADDR), |
| RG_ACCDET_CK_PDN_ADDR, |
| accdet_read(RG_ACCDET_CK_PDN_ADDR), |
| RG_ACCDET_RST_ADDR, |
| accdet_read(RG_ACCDET_RST_ADDR), |
| RG_INT_EN_ACCDET_ADDR, |
| accdet_read(RG_INT_EN_ACCDET_ADDR)); |
| pr_notice("(0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x\n", |
| RG_INT_MASK_ACCDET_ADDR, |
| accdet_read(RG_INT_MASK_ACCDET_ADDR), |
| RG_INT_STATUS_ACCDET_ADDR, |
| accdet_read(RG_INT_STATUS_ACCDET_ADDR), |
| RG_AUDPWDBMICBIAS1_ADDR, |
| accdet_read(RG_AUDPWDBMICBIAS1_ADDR), |
| RG_AUDACCDETMICBIAS0PULLLOW_ADDR, |
| accdet_read(RG_AUDACCDETMICBIAS0PULLLOW_ADDR)); |
| pr_notice("(0x%x)=0x%x (0x%x)=0x%x\n", |
| AUXADC_RQST_CH0_ADDR, |
| accdet_read(AUXADC_RQST_CH0_ADDR), |
| AUXADC_ACCDET_AUTO_SPL_ADDR, |
| accdet_read(AUXADC_ACCDET_AUTO_SPL_ADDR)); |
| pr_notice("(0x%x)=0x%x\n", RG_HPLOUTPUTSTBENH_VAUDP32_ADDR, |
| accdet_read(RG_HPLOUTPUTSTBENH_VAUDP32_ADDR)); |
| |
| pr_notice("accdet_dts:deb0=0x%x,deb1=0x%x,deb3=0x%x,deb4=0x%x\n", |
| cust_pwm_deb->debounce0, cust_pwm_deb->debounce1, |
| cust_pwm_deb->debounce3, cust_pwm_deb->debounce4); |
| } else |
| mini_dump_register(); |
| } |
| |
| static void cat_register(char *buf) |
| { |
| int addr = 0, st_addr = 0, end_addr = 0, idx = 0, ret = 0; |
| |
| ret = sprintf(accdet_log_buf, "[Accdet EINTx support][MODE_%d]regs:\n", |
| accdet_dts.mic_mode); |
| strncat(buf, accdet_log_buf, strlen(accdet_log_buf)); |
| |
| dump_reg = true; |
| dump_register(); |
| dump_reg = false; |
| ret = sprintf(accdet_log_buf, "ACCDET_RG\n"); |
| strncat(buf, accdet_log_buf, strlen(accdet_log_buf)); |
| st_addr = ACCDET_AUXADC_SEL_ADDR; |
| end_addr = ACCDET_MON_FLAG_EN_ADDR; |
| for (addr = st_addr; addr <= end_addr; addr += 8) { |
| idx = addr; |
| ret = sprintf(accdet_log_buf, |
| "(0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x\n", |
| idx, accdet_read(idx), |
| idx+2, accdet_read(idx+2), |
| idx+4, accdet_read(idx+4), |
| idx+6, accdet_read(idx+6)); |
| strncat(buf, accdet_log_buf, strlen(accdet_log_buf)); |
| } |
| ret = sprintf(accdet_log_buf, "AUDDEC_ANA_RG\n"); |
| strncat(buf, accdet_log_buf, strlen(accdet_log_buf)); |
| st_addr = RG_AUDPREAMPLON_ADDR; |
| end_addr = RG_CLKSQ_EN_ADDR; |
| for (addr = st_addr; addr <= end_addr; addr += 8) { |
| idx = addr; |
| ret = sprintf(accdet_log_buf, |
| "(0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x (0x%x)=0x%x\n", |
| idx, accdet_read(idx), |
| idx+2, accdet_read(idx+2), |
| idx+4, accdet_read(idx+4), |
| idx+6, accdet_read(idx+6)); |
| strncat(buf, accdet_log_buf, strlen(accdet_log_buf)); |
| } |
| |
| ret = sprintf(accdet_log_buf, "[0x%x]=0x%x\n", |
| RG_SCK32K_CK_PDN_ADDR, |
| accdet_read(RG_SCK32K_CK_PDN_ADDR)); |
| strncat(buf, accdet_log_buf, strlen(accdet_log_buf)); |
| |
| ret = sprintf(accdet_log_buf, "[0x%x]=0x%x\n", |
| RG_ACCDET_RST_ADDR, |
| accdet_read(RG_ACCDET_RST_ADDR)); |
| strncat(buf, accdet_log_buf, strlen(accdet_log_buf)); |
| |
| ret = sprintf(accdet_log_buf, "[0x%x]=0x%x, [0x%x]=0x%x, [0x%x]=0x%x\n", |
| RG_INT_EN_ACCDET_ADDR, |
| accdet_read(RG_INT_EN_ACCDET_ADDR), |
| RG_INT_MASK_ACCDET_ADDR, |
| accdet_read(RG_INT_MASK_ACCDET_ADDR), |
| RG_INT_STATUS_ACCDET_ADDR, |
| accdet_read(RG_INT_STATUS_ACCDET_ADDR)); |
| strncat(buf, accdet_log_buf, strlen(accdet_log_buf)); |
| |
| ret = sprintf(accdet_log_buf, "[0x%x]=0x%x,[0x%x]=0x%x\n", |
| RG_AUDPWDBMICBIAS1_ADDR, |
| accdet_read(RG_AUDPWDBMICBIAS1_ADDR), |
| RG_AUDACCDETMICBIAS0PULLLOW_ADDR, |
| accdet_read(RG_AUDACCDETMICBIAS0PULLLOW_ADDR)); |
| strncat(buf, accdet_log_buf, strlen(accdet_log_buf)); |
| |
| ret = sprintf(accdet_log_buf, "[0x%x]=0x%x, [0x%x]=0x%x\n", |
| AUXADC_RQST_CH5_ADDR, |
| accdet_read(AUXADC_RQST_CH5_ADDR), |
| AUXADC_ACCDET_AUTO_SPL_ADDR, |
| accdet_read(AUXADC_ACCDET_AUTO_SPL_ADDR)); |
| strncat(buf, accdet_log_buf, strlen(accdet_log_buf)); |
| |
| ret = sprintf(accdet_log_buf, |
| "dtsInfo:deb0=0x%x,deb1=0x%x,deb3=0x%x,deb4=0x%x\n", |
| cust_pwm_deb->debounce0, cust_pwm_deb->debounce1, |
| cust_pwm_deb->debounce3, cust_pwm_deb->debounce4); |
| strncat(buf, accdet_log_buf, strlen(accdet_log_buf)); |
| if (ret < 0) |
| pr_notice("sprintf failed\n"); |
| } |
| |
| static int dbug_thread(void *unused) |
| { |
| dump_register(); |
| |
| return 0; |
| } |
| |
| static ssize_t start_debug_store(struct device_driver *ddri, |
| const char *buf, size_t count) |
| { |
| int error = 0; |
| int ret = 0; |
| |
| if (strlen(buf) < 1) { |
| pr_notice("%s() Invalid input!!\n", __func__); |
| return -EINVAL; |
| } |
| |
| ret = strncmp(buf, "0", 1); |
| /* fix syzkaller issue */ |
| if (debug_thread_en == true) { |
| pr_info("%s() debug thread started, ret!\n", __func__); |
| return count; |
| } |
| |
| if (ret) { |
| debug_thread_en = true; |
| thread = kthread_run(dbug_thread, 0, "ACCDET"); |
| if (IS_ERR(thread)) { |
| error = PTR_ERR(thread); |
| pr_notice("%s() create thread failed,err:%d\n", |
| __func__, error); |
| } else |
| pr_info("%s() start debug thread!\n", __func__); |
| } else { |
| debug_thread_en = false; |
| pr_info("%s() stop debug thread!\n", __func__); |
| } |
| |
| return count; |
| } |
| |
| static ssize_t set_reg_store(struct device_driver *ddri, |
| const char *buf, size_t count) |
| { |
| int ret = 0; |
| u32 addr_tmp = 0; |
| u32 value_tmp = 0; |
| |
| if (strlen(buf) < 3) { |
| pr_notice("%s() Invalid input!!\n", __func__); |
| return -EINVAL; |
| } |
| |
| ret = sscanf(buf, "0x%x,0x%x", &addr_tmp, &value_tmp); |
| if (ret < 0) |
| return ret; |
| |
| pr_info("%s() set addr[0x%x]=0x%x\n", __func__, addr_tmp, value_tmp); |
| |
| if (addr_tmp < TOP0_ANA_ID_ADDR) |
| pr_notice("%s() Illegal addr[0x%x]!!\n", __func__, addr_tmp); |
| else |
| accdet_write(addr_tmp, value_tmp); |
| |
| return count; |
| } |
| |
| static ssize_t dump_reg_show(struct device_driver *ddri, char *buf) |
| { |
| if (buf == NULL) { |
| pr_notice("%s() *buf is NULL\n", __func__); |
| return -EINVAL; |
| } |
| |
| cat_register(buf); |
| pr_info("%s() buf_size:%d\n", __func__, (int)strlen(buf)); |
| |
| return strlen(buf); |
| } |
| |
| static ssize_t dump_reg_store(struct device_driver *ddri, |
| const char *buf, size_t count) |
| { |
| int ret = 0; |
| |
| if (strlen(buf) < 1) { |
| pr_notice("%s() Invalid input!!\n", __func__); |
| return -EINVAL; |
| } |
| |
| ret = strncmp(buf, "0", 1); |
| if (ret) { |
| dump_reg = true; |
| pr_info("%s() start dump regs!\n", __func__); |
| } else { |
| dump_reg = false; |
| pr_info("%s() stop dump regs!\n", __func__); |
| } |
| |
| return count; |
| } |
| |
| static ssize_t set_headset_mode_store(struct device_driver *ddri, |
| const char *buf, size_t count) |
| { |
| int ret = 0; |
| int tmp_headset_mode = 0; |
| |
| if (strlen(buf) < 1) { |
| pr_notice("%s() Invalid input!\n", __func__); |
| return -EINVAL; |
| } |
| |
| ret = kstrtoint(buf, 10, &tmp_headset_mode); |
| if (ret < 0) { |
| pr_notice("%s() kstrtoint failed! ret:%d\n", __func__, ret); |
| return ret; |
| } |
| pr_info("%s() get mic mode: %d\n", __func__, tmp_headset_mode); |
| |
| switch (tmp_headset_mode&0x0F) { |
| case HEADSET_MODE_1: |
| pr_info("%s() Don't support switch to mode_1!\n", __func__); |
| /* accdet_dts.mic_mode = tmp_headset_mode; */ |
| /* accdet_init(); */ |
| break; |
| case HEADSET_MODE_2: |
| accdet_dts.mic_mode = tmp_headset_mode; |
| accdet_init(); |
| accdet_init_debounce(); |
| break; |
| case HEADSET_MODE_6: |
| accdet_dts.mic_mode = tmp_headset_mode; |
| accdet_init(); |
| accdet_init_debounce(); |
| break; |
| default: |
| pr_info("%s() Invalid mode: %d\n", __func__, tmp_headset_mode); |
| break; |
| } |
| accdet_init_once(); |
| |
| return count; |
| } |
| |
| static ssize_t state_show(struct device_driver *ddri, char *buf) |
| { |
| char temp_type = (char)accdet->cable_type; |
| int ret = 0; |
| |
| if (buf == NULL) { |
| pr_notice("[%s] *buf is NULL!\n", __func__); |
| return -EINVAL; |
| } |
| ret = snprintf(buf, 3, "%d\n", temp_type); |
| if (ret < 0) |
| pr_notice("snprintf failed\n"); |
| |
| return strlen(buf); |
| } |
| |
| static DRIVER_ATTR_WO(start_debug); |
| static DRIVER_ATTR_WO(set_reg); |
| static DRIVER_ATTR_RW(dump_reg); |
| static DRIVER_ATTR_WO(set_headset_mode); |
| static DRIVER_ATTR_RO(state); |
| |
| static struct driver_attribute *accdet_attr_list[] = { |
| &driver_attr_start_debug, |
| &driver_attr_set_reg, |
| &driver_attr_dump_reg, |
| &driver_attr_set_headset_mode, |
| &driver_attr_state, |
| }; |
| |
| static int accdet_create_attr(struct device_driver *driver) |
| { |
| int idx, err; |
| int num = ARRAY_SIZE(accdet_attr_list); |
| |
| if (driver == NULL) |
| return -EINVAL; |
| for (idx = 0; idx < num; idx++) { |
| err = driver_create_file(driver, accdet_attr_list[idx]); |
| if (err) { |
| pr_notice("%s() driver_create_file %s err:%d\n", |
| __func__, accdet_attr_list[idx]->attr.name, err); |
| break; |
| } |
| } |
| return err; |
| } |
| |
| /* get plug-in Resister for audio call */ |
| int accdet_read_audio_res(unsigned int res_value) |
| { |
| pr_info("%s() resister value: R=%u(ohm)\n", __func__, res_value); |
| |
| /* if res < 5k ohm normal device; res >= 5k ohm, lineout device */ |
| if (res_value < 5000) |
| return RET_LT_5K; |
| |
| mutex_lock(&accdet->res_lock); |
| if (accdet->eint_sync_flag) { |
| accdet->cable_type = LINE_OUT_DEVICE; |
| accdet->accdet_status = LINE_OUT; |
| send_status_event(accdet->cable_type, 1); |
| pr_info("%s() update state:%d\n", __func__, accdet->cable_type); |
| } |
| mutex_unlock(&accdet->res_lock); |
| |
| return RET_GT_5K; |
| } |
| EXPORT_SYMBOL(accdet_read_audio_res); |
| |
| static u64 accdet_get_current_time(void) |
| { |
| return sched_clock(); |
| } |
| |
| static bool accdet_timeout_ns(u64 start_time_ns, u64 timeout_time_ns) |
| { |
| u64 cur_time = 0; |
| u64 elapse_time = 0; |
| |
| /* get current tick, ns */ |
| cur_time = accdet_get_current_time(); |
| if (cur_time < start_time_ns) { |
| start_time_ns = cur_time; |
| /* 400us */ |
| timeout_time_ns = 400 * 1000; |
| } |
| elapse_time = cur_time - start_time_ns; |
| |
| /* check if timeout */ |
| if (timeout_time_ns <= elapse_time) |
| return false; |
| |
| return true; |
| } |
| |
| static u32 accdet_get_auxadc(void) |
| { |
| int vol = 0, ret = 0; |
| |
| if (!IS_ERR(accdet->accdet_auxadc)) { |
| ret = iio_read_channel_processed(accdet->accdet_auxadc, &vol); |
| if (ret < 0) { |
| pr_notice("Error: %s read fail (%d)\n", __func__, ret); |
| return ret; |
| } |
| } |
| |
| pr_info("%s() vol_val:%d offset:%d real vol:%d mv!\n", __func__, vol, |
| accdet->auxadc_offset, |
| (vol < accdet->auxadc_offset) ? 0 : (vol-accdet->auxadc_offset)); |
| |
| if (vol < accdet->auxadc_offset) |
| vol = 0; |
| else |
| vol -= accdet->auxadc_offset; |
| |
| return vol; |
| } |
| |
| static void accdet_get_efuse(void) |
| { |
| unsigned short efuseval = 0; |
| int ret = 0; |
| int tmp_div; |
| unsigned int moisture_eint0; |
| unsigned int moisture_eint1; |
| |
| /* accdet offset efuse: |
| * this efuse must divided by 2 |
| */ |
| ret = nvmem_device_read(accdet->accdet_efuse, 109*2, 2, &efuseval); |
| accdet->auxadc_offset = efuseval & 0xFF; |
| if (accdet->auxadc_offset > 128) |
| accdet->auxadc_offset -= 256; |
| accdet->auxadc_offset = (accdet->auxadc_offset >> 1); |
| /* all of moisture_vdd/moisture_offset0/eint is 2'complement, |
| * we need to transfer it |
| */ |
| /* moisture vdd efuse offset */ |
| ret = nvmem_device_read(accdet->accdet_efuse, 112*2, 2, &efuseval); |
| accdet->moisture_vdd_offset = |
| (int)((efuseval >> 8) & ACCDET_CALI_MASK0); |
| if (accdet->moisture_vdd_offset > 128) |
| accdet->moisture_vdd_offset -= 256; |
| pr_info("%s moisture_vdd efuse=0x%x, moisture_vdd_offset=%d mv\n", |
| __func__, efuseval, accdet->moisture_vdd_offset); |
| |
| /* moisture offset */ |
| ret = nvmem_device_read(accdet->accdet_efuse, 113*2, 2, &efuseval); |
| accdet->moisture_offset = (int)(efuseval & ACCDET_CALI_MASK0); |
| if (accdet->moisture_offset > 128) |
| accdet->moisture_offset -= 256; |
| pr_info("%s moisture_efuse efuse=0x%x,moisture_offset=%d mv\n", |
| __func__, efuseval, accdet->moisture_offset); |
| |
| if (accdet_dts.moisture_use_ext_res == 0x0) { |
| /* moisture eint efuse offset */ |
| ret = nvmem_device_read(accdet->accdet_efuse, |
| 111*2, 2, &efuseval); |
| moisture_eint0 = |
| (int)((efuseval >> 8) & ACCDET_CALI_MASK0); |
| pr_info("%s moisture_eint0 efuse=0x%x,moisture_eint0=0x%x\n", |
| __func__, efuseval, moisture_eint0); |
| |
| ret = nvmem_device_read(accdet->accdet_efuse, |
| 112*2, 2, &efuseval); |
| moisture_eint1 = (int)(efuseval & ACCDET_CALI_MASK0); |
| pr_info("%s moisture_eint1 efuse=0x%x,moisture_eint1=0x%x\n", |
| __func__, efuseval, moisture_eint1); |
| |
| accdet->moisture_eint_offset = |
| (moisture_eint1 << 8) | moisture_eint0; |
| if (accdet->moisture_eint_offset > 32768) |
| accdet->moisture_eint_offset -= 65536; |
| pr_info("%s moisture_eint_offset=%d ohm\n", __func__, |
| accdet->moisture_eint_offset); |
| |
| accdet->moisture_vm = (2800 + accdet->moisture_vdd_offset); |
| accdet->moisture_vm *= |
| (accdet->water_r + accdet->moisture_int_r); |
| tmp_div = accdet->water_r + accdet->moisture_int_r + |
| 8 * accdet->moisture_eint_offset + 450000; |
| accdet->moisture_vm = accdet->moisture_vm / tmp_div; |
| accdet->moisture_vm = |
| accdet->moisture_vm + accdet->moisture_offset / 2; |
| pr_info("%s internal moisture_vm=%d mv\n", __func__, |
| accdet->moisture_vm); |
| } else if (accdet_dts.moisture_use_ext_res == 0x1) { |
| accdet->moisture_vm = (2800 + accdet->moisture_vdd_offset); |
| accdet->moisture_vm = accdet->moisture_vm * accdet->water_r; |
| accdet->moisture_vm /= |
| (accdet->water_r + accdet->moisture_ext_r); |
| accdet->moisture_vm += |
| (accdet->moisture_offset >> 1); |
| pr_info("%s external moisture_vm=%d mv\n", __func__, |
| accdet->moisture_vm); |
| } |
| pr_info("%s efuse=0x%x,auxadc_val=%dmv\n", __func__, efuseval, |
| accdet->auxadc_offset); |
| } |
| |
| static void accdet_get_efuse_4key(void) |
| { |
| unsigned short tmp_val = 0; |
| unsigned short tmp_8bit = 0; |
| int ret = 0; |
| |
| /* 4-key efuse: |
| * bit[9:2] efuse value is loaded, so every read out value need to be |
| * left shift 2 bit,and then compare with voltage get from AUXADC. |
| * AD efuse: key-A Voltage:0--AD; |
| * DB efuse: key-D Voltage: AD--DB; |
| * BC efuse: key-B Voltage:DB--BC; |
| * key-C Voltage: BC--600; |
| */ |
| ret = nvmem_device_read(accdet->accdet_efuse, 110*2, 2, &tmp_val); |
| tmp_8bit = tmp_val & ACCDET_CALI_MASK0; |
| accdet_dts.four_key.mid = tmp_8bit << 2; |
| |
| tmp_8bit = (tmp_val >> 8) & ACCDET_CALI_MASK0; |
| accdet_dts.four_key.voice = tmp_8bit << 2; |
| |
| ret = nvmem_device_read(accdet->accdet_efuse, 111*2, 2, &tmp_val); |
| tmp_8bit = tmp_val & ACCDET_CALI_MASK0; |
| accdet_dts.four_key.up = tmp_8bit << 2; |
| |
| accdet_dts.four_key.down = 600; |
| pr_info("accdet key thresh: mid=%dmv,voice=%dmv,up=%dmv,down=%dmv\n", |
| accdet_dts.four_key.mid, accdet_dts.four_key.voice, |
| accdet_dts.four_key.up, accdet_dts.four_key.down); |
| } |
| |
| static u32 key_check(u32 v) |
| { |
| if (HAS_CAP(accdet->data->caps, ACCDET_FOUR_KEY)) { |
| if ((v < accdet_dts.four_key.down) && |
| (v >= accdet_dts.four_key.up)) |
| return DW_KEY; |
| if ((v < accdet_dts.four_key.up) && |
| (v >= accdet_dts.four_key.voice)) |
| return UP_KEY; |
| if ((v < accdet_dts.four_key.voice) && |
| (v >= accdet_dts.four_key.mid)) |
| return AS_KEY; |
| if (v < accdet_dts.four_key.mid) |
| return MD_KEY; |
| } else { |
| if ((v < accdet_dts.three_key.down) && |
| (v >= accdet_dts.three_key.up)) |
| return DW_KEY; |
| if ((v < accdet_dts.three_key.up) && |
| (v >= accdet_dts.three_key.mid)) |
| return UP_KEY; |
| if (v < accdet_dts.three_key.mid) |
| return MD_KEY; |
| } |
| return NO_KEY; |
| } |
| |
| static void send_key_event(u32 keycode, u32 flag) |
| { |
| int report = 0; |
| |
| switch (keycode) { |
| case DW_KEY: |
| if (flag != 0) |
| report = SND_JACK_BTN_1; |
| snd_soc_jack_report(&accdet->jack, report, |
| SND_JACK_BTN_1); |
| break; |
| case UP_KEY: |
| if (flag != 0) |
| report = SND_JACK_BTN_2; |
| snd_soc_jack_report(&accdet->jack, report, |
| SND_JACK_BTN_2); |
| break; |
| case MD_KEY: |
| if (flag != 0) |
| report = SND_JACK_BTN_0; |
| snd_soc_jack_report(&accdet->jack, report, |
| SND_JACK_BTN_0); |
| break; |
| case AS_KEY: |
| if (flag != 0) |
| report = SND_JACK_BTN_3; |
| snd_soc_jack_report(&accdet->jack, report, |
| SND_JACK_BTN_3); |
| break; |
| } |
| } |
| |
| static void send_status_event(u32 cable_type, u32 status) |
| { |
| int report = 0; |
| |
| switch (cable_type) { |
| case HEADSET_NO_MIC: |
| if (status) |
| report = SND_JACK_HEADPHONE; |
| else |
| report = 0; |
| snd_soc_jack_report(&accdet->jack, report, |
| SND_JACK_HEADPHONE); |
| /* when plug 4-pole out, if both AB=3 AB=0 happen,3-pole plug |
| * in will be incorrectly reported, then 3-pole plug-out is |
| * reported,if no mantory 4-pole plug-out, icon would be |
| * visible. |
| */ |
| if (status == 0) { |
| report = 0; |
| snd_soc_jack_report(&accdet->jack, report, |
| SND_JACK_MICROPHONE); |
| } |
| pr_info("accdet HEADPHONE(3-pole) %s\n", |
| status ? "PlugIn" : "PlugOut"); |
| break; |
| case HEADSET_MIC: |
| /* when plug 4-pole out, 3-pole plug out should also be |
| * reported for slow plug-in case |
| */ |
| if (status == 0) { |
| report = 0; |
| snd_soc_jack_report(&accdet->jack, report, |
| SND_JACK_HEADPHONE); |
| } |
| if (status) |
| report = SND_JACK_MICROPHONE; |
| else |
| report = 0; |
| |
| snd_soc_jack_report(&accdet->jack, report, |
| SND_JACK_MICROPHONE); |
| pr_info("accdet MICROPHONE(4-pole) %s\n", |
| status ? "PlugIn" : "PlugOut"); |
| break; |
| case LINE_OUT_DEVICE: |
| if (status) |
| report = SND_JACK_LINEOUT; |
| else |
| report = 0; |
| |
| snd_soc_jack_report(&accdet->jack, report, |
| SND_JACK_LINEOUT); |
| pr_info("accdet LineOut %s\n", |
| status ? "PlugIn" : "PlugOut"); |
| break; |
| default: |
| pr_info("%s Invalid cableType\n", __func__); |
| } |
| } |
| |
| static void multi_key_detection(u32 cur_AB) |
| { |
| if (cur_AB == ACCDET_STATE_AB_00) |
| accdet->cur_key = key_check(accdet->cali_voltage); |
| |
| /* delay to fix side effect key when plug-out, when plug-out,seldom |
| * issued AB=0 and Eint, delay to wait eint been flaged in register. |
| * or eint handler issued. accdet->cur_eint_state == PLUG_OUT |
| */ |
| usleep_range(10000, 12000); |
| |
| if (HAS_CAP(accdet->data->caps, ACCDET_AP_GPIO_EINT)) { |
| if (accdet->cur_eint_state == EINT_PLUG_IN) |
| send_key_event(accdet->cur_key, !cur_AB); |
| else |
| accdet->cur_key = NO_KEY; |
| } else { |
| bool irq_bit = false; |
| |
| irq_bit = !(accdet_read(ACCDET_IRQ_ADDR) & |
| ACCDET_EINT_IRQ_B2_B3); |
| /* send key when: |
| * no eint is flaged in reg, and now eint PLUG_IN |
| */ |
| if (irq_bit && (accdet->cur_eint_state == EINT_PLUG_IN)) |
| send_key_event(accdet->cur_key, !cur_AB); |
| else |
| accdet->cur_key = NO_KEY; |
| } |
| |
| if (cur_AB) |
| accdet->cur_key = NO_KEY; |
| } |
| |
| static inline void clear_accdet_int(void) |
| { |
| /* it is safe by using polling to adjust when to clear IRQ_CLR_BIT */ |
| accdet_update_bit(ACCDET_IRQ_ADDR, ACCDET_IRQ_CLR_SFT); |
| } |
| |
| static inline void clear_accdet_int_check(void) |
| { |
| u64 cur_time = accdet_get_current_time(); |
| |
| while ((accdet_read(ACCDET_IRQ_ADDR) & ACCDET_IRQ_MASK_SFT) && |
| (accdet_timeout_ns(cur_time, ACCDET_TIME_OUT))) |
| ; |
| /* clear accdet int, modify for fix interrupt trigger twice error */ |
| accdet_clear_bit(ACCDET_IRQ_ADDR, ACCDET_IRQ_CLR_SFT); |
| accdet_update_bit(RG_INT_STATUS_ACCDET_ADDR, |
| RG_INT_STATUS_ACCDET_SFT); |
| } |
| |
| static inline void clear_accdet_eint(u32 eintid) |
| { |
| if ((eintid & PMIC_EINT0) == PMIC_EINT0) { |
| accdet_update_bit(ACCDET_IRQ_ADDR, |
| ACCDET_EINT0_IRQ_CLR_SFT); |
| } |
| if ((eintid & PMIC_EINT1) == PMIC_EINT1) { |
| accdet_update_bit(ACCDET_IRQ_ADDR, |
| ACCDET_EINT1_IRQ_CLR_SFT); |
| } |
| |
| } |
| |
| static inline void clear_accdet_eint_check(u32 eintid) |
| { |
| u64 cur_time = accdet_get_current_time(); |
| u32 addr = 0; |
| |
| addr = ACCDET_IRQ_ADDR; |
| if ((eintid & PMIC_EINT0) == PMIC_EINT0) { |
| while ((accdet_read(addr) & ACCDET_EINT0_IRQ_MASK_SFT) |
| && (accdet_timeout_ns(cur_time, ACCDET_TIME_OUT))) |
| ; |
| accdet_clear_bit(ACCDET_IRQ_ADDR, |
| ACCDET_EINT0_IRQ_CLR_SFT); |
| accdet_update_bit(RG_INT_STATUS_ACCDET_ADDR, |
| RG_INT_STATUS_ACCDET_EINT0_SFT); |
| } |
| if ((eintid & PMIC_EINT1) == PMIC_EINT1) { |
| while ((accdet_read(addr) & ACCDET_EINT1_IRQ_MASK_SFT) |
| && (accdet_timeout_ns(cur_time, ACCDET_TIME_OUT))) |
| ; |
| accdet_clear_bit(ACCDET_IRQ_ADDR, |
| ACCDET_EINT1_IRQ_CLR_SFT); |
| accdet_update_bit(RG_INT_STATUS_ACCDET_ADDR, |
| RG_INT_STATUS_ACCDET_EINT1_SFT); |
| } |
| } |
| |
| static u32 get_moisture_det_en(void) |
| { |
| return accdet_dts.moisture_detect_enable; |
| } |
| static u32 get_moisture_sw_auxadc_check(void) |
| { |
| unsigned int moisture_vol = 0; |
| |
| if (accdet_dts.moisture_detect_mode == 0x1 || |
| accdet_dts.moisture_detect_mode == 0x2 || |
| accdet_dts.moisture_detect_mode == 0x3) { |
| if (accdet->cur_eint_state == EINT_MOISTURE_DETECTED) { |
| pr_info("%s Moisture plug out detectecd\n", __func__); |
| accdet->cur_eint_state = EINT_PLUG_OUT; |
| return M_PLUG_OUT; |
| } |
| if (accdet->cur_eint_state == EINT_PLUG_OUT) { |
| pr_info("%s now check moisture\n", __func__); |
| moisture_vol = accdet_get_auxadc(); |
| pr_info("moisture_vol:0x%x, moisture_vm:0x%x\r", |
| moisture_vol, accdet->moisture_vm); |
| if (moisture_vol > accdet->moisture_vm) { |
| pr_info("%s water in detectecd!\n", |
| __func__); |
| return M_WATER_IN; |
| } else { |
| return M_HP_PLUG_IN; |
| } |
| pr_info("%s check moisture done,not water.\n", |
| __func__); |
| } |
| } |
| return M_NO_ACT; |
| } |
| |
| static u32 adjust_eint_analog_setting(void) |
| { |
| if ((accdet_dts.eint_detect_mode == 0x3) || |
| (accdet_dts.eint_detect_mode == 0x4)) { |
| /* ESD switches off */ |
| accdet_clear_bit(RG_ACCDETSPARE_ADDR, 8); |
| } |
| if (accdet_dts.eint_detect_mode == 0x4) { |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| /* enable RG_EINT0CONFIGACCDET */ |
| accdet_update_bit(RG_EINT0CONFIGACCDET_ADDR, |
| RG_EINT0CONFIGACCDET_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* enable RG_EINT1CONFIGACCDET */ |
| accdet_update_bit(RG_EINT1CONFIGACCDET_ADDR, |
| RG_EINT1CONFIGACCDET_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* enable RG_EINT0CONFIGACCDET */ |
| accdet_update_bit(RG_EINT0CONFIGACCDET_ADDR, |
| RG_EINT0CONFIGACCDET_SFT); |
| /* enable RG_EINT1CONFIGACCDET */ |
| accdet_update_bit(RG_EINT1CONFIGACCDET_ADDR, |
| RG_EINT1CONFIGACCDET_SFT); |
| } |
| if ((accdet_dts.eint_use_ext_res == 0x3) || |
| (accdet_dts.eint_use_ext_res == 0x4)) { |
| /*select 500k, use internal resistor */ |
| accdet_update_bit(RG_EINT0HIRENB_ADDR, |
| RG_EINT0HIRENB_SFT); |
| } |
| } |
| return 0; |
| } |
| |
| static u32 adjust_eint_digital_setting(void) |
| { |
| unsigned int ret = 0; |
| |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT0)) { |
| /* disable inverter */ |
| accdet_clear_bit(ACCDET_EINT0_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT0_INVERTER_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT1)) { |
| /* disable inverter */ |
| accdet_clear_bit(ACCDET_EINT1_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT1_INVERTER_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_BI_EINT)) { |
| /* disable inverter */ |
| accdet_clear_bit(ACCDET_EINT0_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT0_INVERTER_SW_EN_SFT); |
| /* disable inverter */ |
| accdet_clear_bit(ACCDET_EINT1_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT1_INVERTER_SW_EN_SFT); |
| } |
| if ((accdet_dts.eint_detect_mode == 0x1) || |
| (accdet_dts.eint_detect_mode == 0x2)) { |
| accdet_clear_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT0_CEN_STABLE_SFT); |
| ret = get_moisture_sw_auxadc_check(); |
| /* disable mtest en */ |
| accdet_clear_bit(RG_MTEST_EN_ADDR, |
| RG_MTEST_EN_SFT); |
| accdet_clear_bit(AUDACCDETAUXADCSWCTRL_SEL_ADDR, |
| AUDACCDETAUXADCSWCTRL_SEL_SFT); |
| return ret; |
| } |
| if (accdet_dts.eint_detect_mode == 0x3) { |
| accdet_clear_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT0_CEN_STABLE_SFT); |
| accdet_update_bit(ACCDET_EINT_CTURBO_SEL_ADDR, |
| ACCDET_EINT_CTURBO_SEL_SFT); |
| accdet_update_bit(ACCDET_EINT0_CTURBO_SW_ADDR, |
| ACCDET_EINT0_CTURBO_SW_SFT); |
| pr_info("auxadc T1\r"); |
| ret = get_moisture_sw_auxadc_check(); |
| if ((ret == M_WATER_IN) || (ret == M_HP_PLUG_IN)) { |
| accdet_clear_bit(ACCDET_EINT_CTURBO_SEL_ADDR, |
| ACCDET_EINT_CTURBO_SEL_SFT); |
| accdet_clear_bit(ACCDET_EINT0_CTURBO_SW_ADDR, |
| ACCDET_EINT0_CTURBO_SW_SFT); |
| } |
| return ret; |
| } |
| |
| if (accdet_dts.eint_detect_mode == 0x4) { |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| /* set DA stable signal */ |
| accdet_clear_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT0_CEN_STABLE_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* set DA stable signal */ |
| accdet_clear_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT1_CEN_STABLE_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* set DA stable signal */ |
| accdet_clear_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT0_CEN_STABLE_SFT); |
| /* set DA stable signal */ |
| accdet_clear_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT1_CEN_STABLE_SFT); |
| } |
| } |
| return 0; |
| } |
| static u32 adjust_moisture_digital_setting(u32 eintID) |
| { |
| if ((accdet_dts.moisture_detect_mode == 0x1) || |
| (accdet_dts.moisture_detect_mode == 0x2) || |
| (accdet_dts.moisture_detect_mode == 0x3)) { |
| |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT0)) { |
| /* wk1, enable moisture detection */ |
| accdet_update_bit(ACCDET_EINT0_M_SW_EN_ADDR, |
| ACCDET_EINT0_M_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT1)) { |
| /* wk1, enable moisture detection */ |
| accdet_update_bit(ACCDET_EINT1_M_SW_EN_ADDR, |
| ACCDET_EINT1_M_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_BI_EINT)) { |
| /* wk1, enable moisture detection */ |
| accdet_update_bit(ACCDET_EINT0_M_SW_EN_ADDR, |
| ACCDET_EINT0_M_SW_EN_SFT); |
| accdet_update_bit(ACCDET_EINT1_M_SW_EN_ADDR, |
| ACCDET_EINT1_M_SW_EN_SFT); |
| } |
| } |
| return 0; |
| } |
| |
| static u32 adjust_moisture_analog_setting(u32 eintID) |
| { |
| unsigned int efuseval = 0, vref2val = 0, vref2hi = 0; |
| |
| if (accdet_dts.moisture_detect_mode == 0x1) { |
| /* select VTH to 2.8v(default), can set to 2.4 or 2.0v by dts */ |
| accdet_update_bits(RG_EINTCOMPVTH_ADDR, |
| RG_EINTCOMPVTH_SFT, |
| RG_EINTCOMPVTH_MASK, |
| accdet_dts.moisture_comp_vth); |
| } else if ((accdet_dts.moisture_detect_mode == 0x2) || |
| (accdet_dts.moisture_detect_mode == 0x3)) { |
| /* select VTH to 2.8v(default), can set to 2.4 or 2.0v by dts */ |
| accdet_update_bits(RG_EINTCOMPVTH_ADDR, |
| RG_EINTCOMPVTH_SFT, |
| RG_EINTCOMPVTH_MASK, |
| accdet_dts.moisture_comp_vth); |
| |
| /* Enable mtest en */ |
| accdet_update_bit(RG_MTEST_EN_ADDR, |
| RG_MTEST_EN_SFT); |
| |
| /* select PAD_HP_EINT for moisture detection */ |
| accdet_clear_bit(RG_MTEST_SEL_ADDR, |
| RG_MTEST_SEL_SFT); |
| } else if (accdet_dts.moisture_detect_mode == 0x4) { |
| /* do nothing */ |
| } else if (accdet_dts.moisture_detect_mode == 0x5) { |
| /* enable VREF2 */ |
| vref2hi = 0x1; |
| switch (accdet_dts.moisture_comp_vref2) { |
| case 0: |
| vref2val = 0x3; |
| break; |
| case 1: |
| vref2val = 0x7; |
| break; |
| case 2: |
| vref2val = 0xc; |
| break; |
| default: |
| vref2val = 0x3; |
| break; |
| } |
| pr_info("%s efuse=0x%x,vref2val=0x%x, vref2hi=0x%x\n", |
| __func__, efuseval, vref2val, vref2hi); |
| /* voltage 880~1330mV */ |
| accdet_update_bit(RG_ACCDETSPARE_ADDR, 15); |
| accdet_update_bits(RG_EINTCOMPVTH_ADDR, |
| 6, 0x3, (vref2val & 0xc) >> 2); |
| accdet_update_bits(RG_ACCDETSPARE_ADDR, |
| 13, 0x3, (vref2val & 0x3)); |
| /* golden setting |
| * accdet_update_bits(RG_ACCDETSPARE_ADDR, |
| * 3, 0x1f, 0x1e); |
| */ |
| } |
| return 0; |
| } |
| |
| static u32 adjust_moisture_setting(u32 moistureID, u32 eintID) |
| { |
| unsigned int ret = 0; |
| |
| if (moistureID == M_PLUG_IN) { |
| if (accdet->thing_in_flag == true) { |
| /* receive M_PLUG_IN second time, just clear irq sts */ |
| clear_accdet_eint(eintID); |
| clear_accdet_eint_check(eintID); |
| } else { |
| /* to check if 1st time thing in interrupt */ |
| accdet->thing_in_flag = true; |
| accdet->cur_eint_state = EINT_PLUG_OUT; |
| /* adjust analog moisture setting */ |
| adjust_moisture_analog_setting(eintID); |
| if (accdet_dts.moisture_detect_mode != 0x5) { |
| /* wk2 */ |
| accdet_update_bit(RG_EINT0HIRENB_ADDR, |
| RG_EINT0HIRENB_SFT); |
| } |
| /* adjust digital setting */ |
| ret = adjust_eint_digital_setting(); |
| /* adjust digital moisture setting */ |
| adjust_moisture_digital_setting(eintID); |
| /* sw axuadc check, EINT 1.0~ EINT 2.0 */ |
| clear_accdet_eint(eintID); |
| clear_accdet_eint_check(eintID); |
| if (ret != 0) |
| return ret; |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT0)) { |
| /* wk1, enable moisture detection */ |
| accdet_update_bit(ACCDET_EINT0_M_SW_EN_ADDR, |
| ACCDET_EINT0_M_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* wk1, enable moisture detection */ |
| accdet_update_bit(ACCDET_EINT1_M_SW_EN_ADDR, |
| ACCDET_EINT1_M_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* wk1, enable moisture detection */ |
| accdet_update_bit(ACCDET_EINT0_M_SW_EN_ADDR, |
| ACCDET_EINT0_M_SW_EN_SFT); |
| accdet_update_bit(ACCDET_EINT1_M_SW_EN_ADDR, |
| ACCDET_EINT1_M_SW_EN_SFT); |
| } |
| pr_info("%s() , thing in done\n", __func__); |
| } |
| return M_NO_ACT; |
| } else if (moistureID == M_WATER_IN) { |
| accdet->cur_eint_state = EINT_MOISTURE_DETECTED; |
| if (accdet_dts.moisture_detect_mode == 0x5) { |
| /* Case 5: water in need 128ms to detect plug out |
| * set debounce to 128ms |
| * this value shold less then eint_thresh |
| */ |
| accdet_set_debounce(eint_state011, |
| accdet_dts.pwm_deb.eint_debounce3); |
| } else { |
| /* water in need 0.12ms to detect plug out |
| * set debounce to 0.12ms |
| * this value shold less then eint_thresh |
| */ |
| accdet_set_debounce(eint_state011, 0x1); |
| } |
| clear_accdet_eint(eintID); |
| clear_accdet_eint_check(eintID); |
| return M_NO_ACT; |
| } else if (moistureID == M_HP_PLUG_IN) { |
| /* water in then HP in, recover state */ |
| if (accdet->cur_eint_state == EINT_MOISTURE_DETECTED) |
| accdet->cur_eint_state = EINT_PLUG_OUT; |
| |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT0)) { |
| /* wk1, disable moisture detection */ |
| accdet_clear_bit(ACCDET_EINT0_M_SW_EN_ADDR, |
| ACCDET_EINT0_M_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT1)) { |
| /* wk1, disable moisture detection */ |
| accdet_clear_bit(ACCDET_EINT1_M_SW_EN_ADDR, |
| ACCDET_EINT1_M_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* wk1, disable moisture detection */ |
| accdet_clear_bit(ACCDET_EINT0_M_SW_EN_ADDR, |
| ACCDET_EINT0_M_SW_EN_SFT); |
| accdet_clear_bit(ACCDET_EINT1_M_SW_EN_ADDR, |
| ACCDET_EINT1_M_SW_EN_SFT); |
| } |
| /* wk3, if HP + W together, after detect HP, we should |
| * set accdet_sync_flag to true to avoid receive W interrupt |
| */ |
| accdet->eint_sync_flag = true; |
| |
| adjust_eint_analog_setting(); |
| /* set debounce to 2ms */ |
| accdet_set_debounce(eint_state000, 0x6); |
| } else if (moistureID == M_PLUG_OUT) { |
| /* set debounce to 1ms */ |
| accdet_set_debounce(eint_state000, |
| accdet_dts.pwm_deb.eint_debounce0); |
| } else { |
| pr_debug("should not be here %s()\n", __func__); |
| } |
| return 0; |
| } |
| |
| static u32 adjust_eint_setting(u32 eintsts) |
| { |
| |
| if (eintsts == M_PLUG_IN) { |
| /* adjust digital setting */ |
| adjust_eint_digital_setting(); |
| /* adjust analog setting */ |
| adjust_eint_analog_setting(); |
| } else if (eintsts == M_PLUG_OUT) { |
| /* set debounce to 1ms */ |
| accdet_set_debounce(eint_state000, |
| accdet_dts.pwm_deb.eint_debounce0); |
| } else { |
| pr_debug("should not be here %s()\n", __func__); |
| } |
| |
| return 0; |
| } |
| |
| static void recover_eint_analog_setting(void) |
| { |
| if ((accdet_dts.eint_detect_mode == 0x3) || |
| (accdet_dts.eint_detect_mode == 0x4)) { |
| /* ESD switches on */ |
| accdet_update_bit(RG_ACCDETSPARE_ADDR, 8); |
| } |
| if (accdet_dts.eint_detect_mode == 0x4) { |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| /* disable RG_EINT0CONFIGACCDET */ |
| accdet_clear_bit(RG_EINT0CONFIGACCDET_ADDR, |
| RG_EINT0CONFIGACCDET_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* disable RG_EINT1CONFIGACCDET */ |
| accdet_clear_bit(RG_EINT1CONFIGACCDET_ADDR, |
| RG_EINT1CONFIGACCDET_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* disable RG_EINT0CONFIGACCDET */ |
| accdet_clear_bit(RG_EINT0CONFIGACCDET_ADDR, |
| RG_EINT0CONFIGACCDET_SFT); |
| /* disable RG_EINT0CONFIGACCDET */ |
| accdet_clear_bit(RG_EINT1CONFIGACCDET_ADDR, |
| RG_EINT1CONFIGACCDET_SFT); |
| } |
| accdet_clear_bit(RG_EINT0HIRENB_ADDR, |
| RG_EINT0HIRENB_SFT); |
| } |
| |
| } |
| static void recover_eint_digital_setting(void) |
| { |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| accdet_clear_bit(ACCDET_EINT0_M_SW_EN_ADDR, |
| ACCDET_EINT0_M_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| accdet_clear_bit(ACCDET_EINT1_M_SW_EN_ADDR, |
| ACCDET_EINT1_M_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| accdet_clear_bit(ACCDET_EINT0_M_SW_EN_ADDR, |
| ACCDET_EINT0_M_SW_EN_SFT); |
| accdet_clear_bit(ACCDET_EINT1_M_SW_EN_ADDR, |
| ACCDET_EINT1_M_SW_EN_SFT); |
| } |
| if (accdet_dts.eint_detect_mode == 0x4) { |
| /* enable eint0cen */ |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| /* enable eint0cen */ |
| accdet_update_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT0_CEN_STABLE_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* enable eint1cen */ |
| accdet_update_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT1_CEN_STABLE_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* enable eint0cen */ |
| accdet_update_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT0_CEN_STABLE_SFT); |
| /* enable eint1cen */ |
| accdet_update_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT1_CEN_STABLE_SFT); |
| } |
| } |
| |
| if (accdet_dts.eint_detect_mode != 0x1) { |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| /* enable inverter */ |
| accdet_update_bit(ACCDET_EINT0_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT0_INVERTER_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* enable inverter */ |
| accdet_update_bit(ACCDET_EINT1_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT1_INVERTER_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* enable inverter */ |
| accdet_update_bit(ACCDET_EINT0_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT0_INVERTER_SW_EN_SFT); |
| /* enable inverter */ |
| accdet_update_bit(ACCDET_EINT1_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT1_INVERTER_SW_EN_SFT); |
| } |
| } |
| } |
| |
| static void recover_moisture_analog_setting(void) |
| { |
| if (accdet_dts.moisture_detect_mode == 0x1) { |
| /* select VTH to 2v */ |
| accdet_update_bits(RG_EINTCOMPVTH_ADDR, |
| RG_EINTCOMPVTH_SFT, |
| RG_EINTCOMPVTH_MASK, 0x2); |
| } else if (accdet_dts.moisture_detect_mode == 0x2) { |
| } else if (accdet_dts.moisture_detect_mode == 0x3) { |
| } else if (accdet_dts.moisture_detect_mode == 0x4) { |
| } else if (accdet_dts.moisture_detect_mode == 0x5) { |
| /* enable comp1 delay window */ |
| accdet_clear_bit(RG_EINT0NOHYS_ADDR, |
| RG_EINT0NOHYS_SFT); |
| |
| accdet_set_debounce(eint_state011, |
| accdet_dts.pwm_deb.eint_debounce3); |
| /* disconnect VREF2 to EINT0CMP and recover to vref */ |
| accdet_update_bits(RG_EINTCOMPVTH_ADDR, |
| RG_EINTCOMPVTH_SFT, RG_EINTCOMPVTH_MASK, |
| accdet_dts.moisture_comp_vth); |
| } |
| } |
| |
| static void recover_moisture_setting(u32 moistureID) |
| { |
| if (moistureID == M_HP_PLUG_IN) { |
| /* set debounce to 2ms */ |
| accdet_set_debounce(eint_state000, 0x6); |
| } else if (moistureID == M_PLUG_OUT) { |
| /* set debounce to 1ms */ |
| accdet_set_debounce(eint_state000, |
| accdet_dts.pwm_deb.eint_debounce0); |
| recover_eint_analog_setting(); |
| recover_moisture_analog_setting(); |
| recover_eint_digital_setting(); |
| pr_info("%s done\n", __func__); |
| |
| } |
| } |
| |
| static void recover_eint_setting(u32 eintsts) |
| { |
| if (eintsts == M_PLUG_OUT) { |
| recover_eint_analog_setting(); |
| recover_eint_digital_setting(); |
| } |
| } |
| |
| static u32 get_triggered_eint(void) |
| { |
| u32 eint_ID = NO_PMIC_EINT; |
| u32 irq_status = accdet_read(ACCDET_IRQ_ADDR); |
| |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| if ((irq_status & ACCDET_EINT0_IRQ_MASK_SFT) == |
| ACCDET_EINT0_IRQ_MASK_SFT) |
| eint_ID = PMIC_EINT0; |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| if ((irq_status & ACCDET_EINT1_IRQ_MASK_SFT) == |
| ACCDET_EINT1_IRQ_MASK_SFT) |
| eint_ID = PMIC_EINT1; |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| if ((irq_status & ACCDET_EINT0_IRQ_MASK_SFT) == |
| ACCDET_EINT0_IRQ_MASK_SFT) |
| eint_ID |= PMIC_EINT0; |
| if ((irq_status & ACCDET_EINT1_IRQ_MASK_SFT) == |
| ACCDET_EINT1_IRQ_MASK_SFT) |
| eint_ID |= PMIC_EINT1; |
| } |
| return eint_ID; |
| } |
| |
| static inline void enable_accdet(u32 state_swctrl) |
| { |
| /* enable ACCDET unit */ |
| accdet_update_bit(ACCDET_SW_EN_ADDR, ACCDET_SW_EN_SFT); |
| } |
| |
| static inline void disable_accdet(void) |
| { |
| /* sync with accdet_irq_handler set clear accdet irq bit to avoid to |
| * set clear accdet irq bit after disable accdet disable accdet irq |
| */ |
| clear_accdet_int(); |
| udelay(200); |
| mutex_lock(&accdet->res_lock); |
| clear_accdet_int_check(); |
| mutex_unlock(&accdet->res_lock); |
| |
| /* recover accdet debounce0,3 */ |
| accdet_set_debounce(accdet_state000, cust_pwm_deb->debounce0); |
| accdet_set_debounce(accdet_state001, cust_pwm_deb->debounce1); |
| accdet_set_debounce(accdet_state011, cust_pwm_deb->debounce3); |
| } |
| |
| static inline void headset_plug_out(void) |
| { |
| send_status_event(accdet->cable_type, 0); |
| accdet->accdet_status = PLUG_OUT; |
| accdet->cable_type = NO_DEVICE; |
| |
| if (accdet->cur_key != 0) { |
| send_key_event(accdet->cur_key, 0); |
| accdet->cur_key = 0; |
| } |
| dis_micbias_done = false; |
| pr_info("accdet %s, set cable_type = NO_DEVICE %d\n", __func__, |
| dis_micbias_done); |
| } |
| static void dis_micbias_timerhandler(struct timer_list *t) |
| { |
| int ret = 0; |
| |
| ret = queue_work(accdet->dis_micbias_workqueue, |
| &accdet->dis_micbias_work); |
| if (!ret) |
| pr_notice("Error: %s (%d)\n", __func__, ret); |
| } |
| |
| static void dis_micbias_work_callback(struct work_struct *work) |
| { |
| u32 cur_AB = 0, eintID = 0; |
| |
| /* check EINT0 status, if plug out, |
| * not need to disable accdet here |
| */ |
| eintID = accdet_read_bits(ACCDET_EINT0_MEM_IN_ADDR, |
| ACCDET_EINT0_MEM_IN_SFT, |
| ACCDET_EINT0_MEM_IN_MASK); |
| if (eintID == M_PLUG_OUT) { |
| pr_notice("%s Plug-out, no dis micbias\n", __func__); |
| return; |
| } |
| /* if modify_vref_volt called, not need to dis micbias again */ |
| if (dis_micbias_done == true) { |
| pr_notice("%s modify_vref_volt called\n", __func__); |
| return; |
| } |
| |
| cur_AB = accdet_read(ACCDET_MEM_IN_ADDR) >> ACCDET_STATE_MEM_IN_OFFSET; |
| cur_AB = cur_AB & ACCDET_STATE_AB_MASK; |
| |
| /* if 3pole disable accdet |
| * if <20k + 4pole, disable accdet will disable accdet |
| * plug out interrupt. The behavior will same as 3pole |
| */ |
| if ((accdet->cable_type == HEADSET_NO_MIC) || |
| (cur_AB == ACCDET_STATE_AB_00) || |
| (cur_AB == ACCDET_STATE_AB_11)) { |
| /* disable accdet_sw_en=0 |
| * disable accdet_hwmode_en=0 |
| */ |
| accdet_clear_bit(ACCDET_SW_EN_ADDR, |
| ACCDET_SW_EN_SFT); |
| disable_accdet(); |
| } |
| } |
| |
| static void eint_work_callback(struct work_struct *work) |
| { |
| if (accdet->cur_eint_state == EINT_PLUG_IN) { |
| /* disable vusb LP */ |
| accdet_write(RG_LDO_VUSB_HW0_OP_EN_ADDR, 0x8000); |
| |
| mutex_lock(&accdet->res_lock); |
| accdet->eint_sync_flag = true; |
| mutex_unlock(&accdet->res_lock); |
| __pm_wakeup_event(accdet->timer_lock, |
| jiffies_to_msecs(7 * HZ)); |
| |
| accdet_init(); |
| |
| enable_accdet(0); |
| } else { |
| mutex_lock(&accdet->res_lock); |
| accdet->eint_sync_flag = false; |
| accdet->thing_in_flag = false; |
| mutex_unlock(&accdet->res_lock); |
| if (accdet_dts.moisture_detect_mode != 0x5) |
| del_timer_sync(&micbias_timer); |
| |
| /* disable accdet_sw_en=0 |
| */ |
| accdet_clear_bit(ACCDET_SW_EN_ADDR, |
| ACCDET_SW_EN_SFT); |
| disable_accdet(); |
| headset_plug_out(); |
| } |
| |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT_IRQ)) { |
| if (get_moisture_det_en() == 0x1) |
| recover_moisture_setting(accdet->eint_id); |
| else |
| recover_eint_setting(accdet->eint_id); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_AP_GPIO_EINT)) |
| enable_irq(accdet->gpioirq); |
| } |
| |
| void accdet_set_debounce(int state, unsigned int debounce) |
| { |
| switch (state) { |
| case accdet_state000: |
| /* set ACCDET debounce value = debounce/32 ms */ |
| accdet_write(ACCDET_DEBOUNCE0_ADDR, debounce); |
| break; |
| case accdet_state001: |
| accdet_write(ACCDET_DEBOUNCE1_ADDR, debounce); |
| break; |
| case accdet_state010: |
| accdet_write(ACCDET_DEBOUNCE2_ADDR, debounce); |
| break; |
| case accdet_state011: |
| accdet_write(ACCDET_DEBOUNCE3_ADDR, debounce); |
| break; |
| case accdet_auxadc: |
| /* set auxadc debounce:0x42(2ms) */ |
| accdet_write(ACCDET_CONNECT_AUXADC_TIME_DIG_ADDR, |
| debounce); |
| break; |
| case eint_state000: |
| accdet_update_bits(ACCDET_EINT_DEBOUNCE0_ADDR, |
| ACCDET_EINT_DEBOUNCE0_SFT, |
| ACCDET_EINT_DEBOUNCE0_MASK, |
| debounce); |
| break; |
| case eint_state001: |
| accdet_update_bits(ACCDET_EINT_DEBOUNCE1_ADDR, |
| ACCDET_EINT_DEBOUNCE1_SFT, |
| ACCDET_EINT_DEBOUNCE1_MASK, |
| debounce); |
| break; |
| case eint_state010: |
| accdet_update_bits(ACCDET_EINT_DEBOUNCE2_ADDR, |
| ACCDET_EINT_DEBOUNCE2_SFT, |
| ACCDET_EINT_DEBOUNCE2_MASK, |
| debounce); |
| break; |
| case eint_state011: |
| accdet_update_bits(ACCDET_EINT_DEBOUNCE3_ADDR, |
| ACCDET_EINT_DEBOUNCE3_SFT, |
| ACCDET_EINT_DEBOUNCE3_MASK, |
| debounce); |
| break; |
| case eint_inverter_state000: |
| accdet_write(ACCDET_EINT_INVERTER_DEBOUNCE_ADDR, |
| debounce); |
| break; |
| default: |
| pr_notice("Error: %s error state (%d)\n", __func__, state); |
| break; |
| } |
| } |
| |
| static inline void check_cable_type(void) |
| { |
| u32 cur_AB = 0; |
| |
| cur_AB = accdet_read(ACCDET_MEM_IN_ADDR) >> ACCDET_STATE_MEM_IN_OFFSET; |
| cur_AB = cur_AB & ACCDET_STATE_AB_MASK; |
| |
| accdet->button_status = 0; |
| |
| switch (accdet->accdet_status) { |
| case PLUG_OUT: |
| if (cur_AB == ACCDET_STATE_AB_00) { |
| mutex_lock(&accdet->res_lock); |
| if (accdet->eint_sync_flag) { |
| accdet->cable_type = HEADSET_NO_MIC; |
| accdet->accdet_status = HOOK_SWITCH; |
| } else |
| pr_notice("accdet hp has been plug-out\n"); |
| mutex_unlock(&accdet->res_lock); |
| /* for IOT HP */ |
| accdet_set_debounce(eint_state011, |
| accdet_dts.pwm_deb.eint_debounce3); |
| } else if (cur_AB == ACCDET_STATE_AB_01) { |
| mutex_lock(&accdet->res_lock); |
| if (accdet->eint_sync_flag) { |
| accdet->accdet_status = MIC_BIAS; |
| accdet->cable_type = HEADSET_MIC; |
| } else |
| pr_notice("accdet hp has been plug-out\n"); |
| mutex_unlock(&accdet->res_lock); |
| /* solution: adjust hook switch debounce time |
| * for fast key press condition, avoid to miss key |
| */ |
| accdet_set_debounce(accdet_state000, |
| button_press_debounce); |
| |
| /* adjust debounce1 to original 0x800(64ms), |
| * to fix miss key issue when fast press double key. |
| */ |
| accdet_set_debounce(accdet_state001, |
| button_press_debounce_01); |
| /* for IOT HP */ |
| accdet_set_debounce(eint_state011, 0x1); |
| } else if (cur_AB == ACCDET_STATE_AB_11) { |
| /* accdet PLUG_OUT state not change */ |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT_IRQ)) { |
| mutex_lock(&accdet->res_lock); |
| if (accdet->eint_sync_flag) { |
| accdet->accdet_status = PLUG_OUT; |
| accdet->cable_type = NO_DEVICE; |
| } else |
| pr_notice("accdet hp been plug-out\n"); |
| mutex_unlock(&accdet->res_lock); |
| } |
| } else { |
| pr_notice("accdet %s Invalid AB.Do nothing\n", |
| __func__); |
| } |
| break; |
| case MIC_BIAS: |
| if (cur_AB == ACCDET_STATE_AB_00) { |
| mutex_lock(&accdet->res_lock); |
| if (accdet->eint_sync_flag) { |
| accdet->button_status = 1; |
| accdet->accdet_status = HOOK_SWITCH; |
| multi_key_detection(cur_AB); |
| } else |
| pr_notice("accdet hp has been plug-out\n"); |
| mutex_unlock(&accdet->res_lock); |
| } else if (cur_AB == ACCDET_STATE_AB_01) { |
| mutex_lock(&accdet->res_lock); |
| if (accdet->eint_sync_flag) { |
| accdet->accdet_status = MIC_BIAS; |
| accdet->cable_type = HEADSET_MIC; |
| /* accdet MIC_BIAS state not change */ |
| } else |
| pr_notice("accdet hp has been plug-out\n"); |
| mutex_unlock(&accdet->res_lock); |
| /* for IOT HP */ |
| accdet_set_debounce(eint_state011, 0x1); |
| } else if (cur_AB == ACCDET_STATE_AB_11) { |
| /* accdet Don't send plug out in MIC_BIAS */ |
| mutex_lock(&accdet->res_lock); |
| if (accdet->eint_sync_flag) |
| accdet->accdet_status = PLUG_OUT; |
| else |
| pr_notice("accdet hp has been plug-out\n"); |
| mutex_unlock(&accdet->res_lock); |
| } else { |
| pr_notice("accdet %s Invalid AB.Do nothing\n", |
| __func__); |
| } |
| break; |
| case HOOK_SWITCH: |
| if (cur_AB == ACCDET_STATE_AB_00) { |
| } else if (cur_AB == ACCDET_STATE_AB_01) { |
| mutex_lock(&accdet->res_lock); |
| if (accdet->eint_sync_flag) { |
| multi_key_detection(cur_AB); |
| accdet->accdet_status = MIC_BIAS; |
| accdet->cable_type = HEADSET_MIC; |
| } else |
| pr_notice("accdet hp has been plug-out\n"); |
| mutex_unlock(&accdet->res_lock); |
| |
| /* adjust debounce0 and debounce1 to fix miss key issue. |
| */ |
| accdet_set_debounce(accdet_state000, |
| button_press_debounce); |
| accdet_set_debounce(accdet_state001, |
| button_press_debounce_01); |
| /* for IOT HP */ |
| accdet_set_debounce(eint_state011, 0x1); |
| } else if (cur_AB == ACCDET_STATE_AB_11) { |
| /* accdet Don't send plugout in HOOK_SWITCH */ |
| mutex_lock(&accdet->res_lock); |
| if (accdet->eint_sync_flag) |
| accdet->accdet_status = PLUG_OUT; |
| else |
| pr_notice("accdet hp has been plug-out\n"); |
| mutex_unlock(&accdet->res_lock); |
| } else { |
| pr_notice("accdet %s Invalid AB.Do nothing\n", |
| __func__); |
| } |
| break; |
| case STAND_BY: |
| /* accdet %s STANDBY state.Err!Do nothing */ |
| break; |
| default: |
| /* accdet %s Error state.Do nothing */ |
| break; |
| } |
| } |
| |
| static void accdet_work_callback(struct work_struct *work) |
| { |
| u32 pre_cable_type = accdet->cable_type; |
| |
| __pm_stay_awake(accdet->wake_lock); |
| check_cable_type(); |
| |
| mutex_lock(&accdet->res_lock); |
| if (accdet->eint_sync_flag) { |
| if (pre_cable_type != accdet->cable_type) |
| send_status_event(accdet->cable_type, 1); |
| } |
| mutex_unlock(&accdet->res_lock); |
| if (accdet->cable_type != NO_DEVICE) { |
| accdet_modify_vref_volt_self(); |
| /* enable vusb LP */ |
| accdet_write(RG_LDO_VUSB_HW0_OP_EN_ADDR, 0x8005); |
| } |
| |
| __pm_relax(accdet->wake_lock); |
| } |
| |
| static void accdet_queue_work(void) |
| { |
| int ret; |
| |
| if (accdet->accdet_status == MIC_BIAS) |
| accdet->cali_voltage = accdet_get_auxadc(); |
| |
| ret = queue_work(accdet->accdet_workqueue, &accdet->accdet_work); |
| if (!ret) |
| pr_notice("Error: %s (%d)\n", __func__, ret); |
| } |
| |
| static int pmic_eint_queue_work(int eintID) |
| { |
| int ret = 0, mode = 0; |
| |
| if (accdet->cur_eint_state == EINT_MOISTURE_DETECTED) { |
| pr_info("%s water in then plug out, handle plugout\r", |
| __func__); |
| accdet->cur_eint_state = EINT_PLUG_OUT; |
| ret = queue_work(accdet->eint_workqueue, &accdet->eint_work); |
| return 0; |
| } |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT0)) { |
| if (eintID == PMIC_EINT0) { |
| if (accdet->cur_eint_state == EINT_PLUG_IN) { |
| accdet_set_debounce(accdet_state011, |
| cust_pwm_deb->debounce3); |
| accdet->cur_eint_state = EINT_PLUG_OUT; |
| } else { |
| if (accdet->eint_id != M_PLUG_OUT) { |
| accdet->cur_eint_state = EINT_PLUG_IN; |
| mode = accdet_dts.moisture_detect_mode; |
| if (mode != 0x5) { |
| mod_timer(&micbias_timer, |
| jiffies+MICBIAS_DISABLE_TIMER); |
| } |
| } |
| } |
| ret = queue_work(accdet->eint_workqueue, |
| &accdet->eint_work); |
| } else |
| pr_notice("%s invalid EINT ID!\n", __func__); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT1)) { |
| if (eintID == PMIC_EINT1) { |
| if (accdet->cur_eint_state == EINT_PLUG_IN) { |
| accdet_set_debounce(accdet_state011, |
| cust_pwm_deb->debounce3); |
| accdet->cur_eint_state = EINT_PLUG_OUT; |
| } else { |
| if (accdet->eint_id != M_PLUG_OUT) { |
| accdet->cur_eint_state = EINT_PLUG_IN; |
| mode = accdet_dts.moisture_detect_mode; |
| if (mode != 0x5) { |
| mod_timer(&micbias_timer, |
| jiffies+MICBIAS_DISABLE_TIMER); |
| } |
| } |
| } |
| ret = queue_work(accdet->eint_workqueue, |
| &accdet->eint_work); |
| } else |
| pr_notice("%s invalid EINT ID!\n", __func__); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_BI_EINT)) { |
| if ((eintID & PMIC_EINT0) == PMIC_EINT0) { |
| if (accdet->eint0_state == EINT_PLUG_IN) { |
| accdet_set_debounce(accdet_state011, |
| cust_pwm_deb->debounce3); |
| accdet->eint0_state = EINT_PLUG_OUT; |
| } else { |
| if (accdet->eint_id != M_PLUG_OUT) |
| accdet->eint0_state = EINT_PLUG_IN; |
| } |
| } |
| if ((eintID & PMIC_EINT1) == PMIC_EINT1) { |
| if (accdet->eint1_state == EINT_PLUG_IN) { |
| accdet_set_debounce(accdet_state011, |
| cust_pwm_deb->debounce3); |
| accdet->eint1_state = EINT_PLUG_OUT; |
| } else { |
| if (accdet->eint_id != M_PLUG_OUT) |
| accdet->eint1_state = EINT_PLUG_IN; |
| } |
| } |
| |
| /* bi_eint trigger issued current state, may */ |
| if (accdet->cur_eint_state == EINT_PLUG_OUT) { |
| accdet->cur_eint_state = |
| accdet->eint0_state & accdet->eint1_state; |
| if (accdet->cur_eint_state == EINT_PLUG_IN) { |
| if (accdet_dts.moisture_detect_mode != 0x5) { |
| mod_timer(&micbias_timer, |
| jiffies + MICBIAS_DISABLE_TIMER); |
| } |
| ret = queue_work(accdet->eint_workqueue, |
| &accdet->eint_work); |
| } |
| } else if (accdet->cur_eint_state == EINT_PLUG_IN) { |
| if ((accdet->eint0_state|accdet->eint1_state) |
| == EINT_PLUG_OUT) { |
| clear_accdet_eint_check(PMIC_EINT0); |
| clear_accdet_eint_check(PMIC_EINT1); |
| } else if ((accdet->eint0_state & accdet->eint1_state) |
| == EINT_PLUG_OUT) { |
| accdet->cur_eint_state = EINT_PLUG_OUT; |
| ret = queue_work(accdet->eint_workqueue, |
| &accdet->eint_work); |
| } |
| } |
| } |
| return ret; |
| } |
| |
| static u32 config_moisture_detect_1_0(void) |
| { |
| /* Disable ACCDET to AUXADC */ |
| accdet_clear_bit(RG_ACCDET2AUXSWEN_ADDR, |
| RG_ACCDET2AUXSWEN_SFT); |
| accdet_update_bit(AUDACCDETAUXADCSWCTRL_SEL_ADDR, |
| AUDACCDETAUXADCSWCTRL_SEL_SFT); |
| accdet_clear_bit(AUDACCDETAUXADCSWCTRL_SW_ADDR, |
| AUDACCDETAUXADCSWCTRL_SW_SFT); |
| |
| /* Enable moisture detection */ |
| accdet_update_bit(RG_MTEST_EN_ADDR, RG_MTEST_EN_SFT); |
| |
| /* select PAD_HP_EINT for moisture detection */ |
| accdet_clear_bit(RG_MTEST_SEL_ADDR, RG_MTEST_SEL_SFT); |
| |
| /* select VTH to 2v */ |
| accdet_update_bits(RG_EINTCOMPVTH_ADDR, |
| RG_EINTCOMPVTH_SFT, RG_EINTCOMPVTH_MASK, 0x2); |
| |
| return 0; |
| } |
| |
| static u32 config_moisture_detect_1_1(void) |
| { |
| /* Disable ACCDET to AUXADC */ |
| accdet_clear_bit(RG_ACCDET2AUXSWEN_ADDR, |
| RG_ACCDET2AUXSWEN_SFT); |
| accdet_update_bit(AUDACCDETAUXADCSWCTRL_SEL_ADDR, |
| AUDACCDETAUXADCSWCTRL_SEL_SFT); |
| accdet_clear_bit(AUDACCDETAUXADCSWCTRL_SW_ADDR, |
| AUDACCDETAUXADCSWCTRL_SW_SFT); |
| |
| /* select VTH to 2v */ |
| accdet_update_bits(RG_EINTCOMPVTH_ADDR, |
| RG_EINTCOMPVTH_SFT, RG_EINTCOMPVTH_MASK, 0x2); |
| |
| return 0; |
| } |
| |
| static u32 config_moisture_detect_2_1(void) |
| { |
| u32 efuseval = 0, eintvth = 0, ret = 0; |
| /* select VTH to 2.8v(default), can set to 2.4 or 2.0v by dts */ |
| accdet_update_bits(RG_EINTCOMPVTH_ADDR, |
| RG_EINTCOMPVTH_SFT, RG_EINTCOMPVTH_MASK, |
| accdet_dts.moisture_comp_vth); |
| /* 2. Set RG_EINT0CTURBO<2:0> ; 75k~15k |
| * read efuse: |
| * 3. Set RG_ACCDETSPARE<7:3> ; VREF2 |
| * read efuse: |
| */ |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT0)) { |
| /* enable cturbo setting */ |
| accdet_update_bit(RG_EINT0CTURBO_ADDR, |
| RG_EINT0CTURBO_SFT); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT1)) { |
| accdet_update_bit(RG_EINT1CTURBO_ADDR, |
| RG_EINT1CTURBO_SFT); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_BI_EINT)) { |
| accdet_update_bit(RG_EINT0CTURBO_ADDR, |
| RG_EINT0CTURBO_SFT); |
| accdet_update_bit(RG_EINT1CTURBO_ADDR, |
| RG_EINT1CTURBO_SFT); |
| } |
| /* set moisture reference voltage MVTH |
| * golden setting |
| * accdet_update_bits(RG_ACCDETSPARE_ADDR, 0x3, 0x1F, 0xA); |
| */ |
| |
| /* EINTVTH1K/5K/10K efuse */ |
| ret = nvmem_device_read(accdet->accdet_efuse, 114*2, 2, &efuseval); |
| eintvth = (int)(efuseval & ACCDET_CALI_MASK0); |
| pr_info("%s moisture_eint0 efuse=0x%x,eintvth=0x%x\n", |
| __func__, efuseval, eintvth); |
| |
| /* set moisture reference voltage MVTH */ |
| accdet_update_bits(RG_ACCDETSPARE_ADDR, 0x3, 0x1F, eintvth); |
| return 0; |
| } |
| |
| static u32 config_moisture_detect_2_1_1(void) |
| { |
| /* select VTH to 2.8v(default), can set to 2.4 or 2.0v by dts */ |
| accdet_update_bits(RG_EINTCOMPVTH_ADDR, |
| RG_EINTCOMPVTH_SFT, RG_EINTCOMPVTH_MASK, |
| accdet_dts.moisture_comp_vth); |
| |
| return 0; |
| } |
| void accdet_irq_handle(void) |
| { |
| u32 eintID = 0, ret = 0; |
| u32 irq_status = 0, acc_sts = 0, eint_sts = 0; |
| |
| eintID = get_triggered_eint(); |
| irq_status = accdet_read(ACCDET_IRQ_ADDR); |
| acc_sts = accdet_read(ACCDET_MEM_IN_ADDR); |
| eint_sts = accdet_read(ACCDET_EINT0_MEM_IN_ADDR); |
| |
| if ((irq_status & ACCDET_IRQ_MASK_SFT) && (eintID == 0)) { |
| clear_accdet_int(); |
| accdet_queue_work(); |
| clear_accdet_int_check(); |
| } else if (eintID != NO_PMIC_EINT) { |
| /* check EINT0 status */ |
| accdet->eint_id = accdet_read_bits( |
| ACCDET_EINT0_MEM_IN_ADDR, |
| ACCDET_EINT0_MEM_IN_SFT, |
| ACCDET_EINT0_MEM_IN_MASK); |
| if (get_moisture_det_en() == 0x1) { |
| /* adjust moisture digital/analog setting */ |
| ret = adjust_moisture_setting(accdet->eint_id, eintID); |
| if (ret == M_NO_ACT) |
| return; |
| } else { |
| /* adjust eint digital/analog setting */ |
| adjust_eint_setting(accdet->eint_id); |
| } |
| clear_accdet_eint(eintID); |
| clear_accdet_eint_check(eintID); |
| pmic_eint_queue_work(eintID); |
| } else { |
| pr_notice("%s no interrupt detected!\n", __func__); |
| } |
| } |
| |
| static irqreturn_t mtk_accdet_irq_handler_thread(int irq, void *data) |
| { |
| accdet_irq_handle(); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t ex_eint_handler(int irq, void *data) |
| { |
| int ret = 0; |
| |
| if (accdet->cur_eint_state == EINT_PLUG_IN) { |
| /* To trigger EINT when the headset was plugged in |
| * We set the polarity back as we initialed. |
| */ |
| if (accdet->accdet_eint_type == IRQ_TYPE_LEVEL_HIGH) |
| irq_set_irq_type(accdet->gpioirq, IRQ_TYPE_LEVEL_HIGH); |
| else |
| irq_set_irq_type(accdet->gpioirq, IRQ_TYPE_LEVEL_LOW); |
| gpio_set_debounce(accdet->gpiopin, accdet->gpio_hp_deb); |
| |
| accdet->cur_eint_state = EINT_PLUG_OUT; |
| } else { |
| /* To trigger EINT when the headset was plugged out |
| * We set the opposite polarity to what we initialed. |
| */ |
| if (accdet->accdet_eint_type == IRQ_TYPE_LEVEL_HIGH) |
| irq_set_irq_type(accdet->gpioirq, IRQ_TYPE_LEVEL_LOW); |
| else |
| irq_set_irq_type(accdet->gpioirq, IRQ_TYPE_LEVEL_HIGH); |
| |
| gpio_set_debounce(accdet->gpiopin, |
| accdet_dts.plugout_deb * 1000); |
| |
| accdet->cur_eint_state = EINT_PLUG_IN; |
| |
| if (accdet_dts.moisture_detect_mode != 0x5) { |
| mod_timer(&micbias_timer, |
| jiffies + MICBIAS_DISABLE_TIMER); |
| } |
| |
| } |
| |
| disable_irq_nosync(accdet->gpioirq); |
| ret = queue_work(accdet->eint_workqueue, &accdet->eint_work); |
| return IRQ_HANDLED; |
| } |
| |
| static inline int ext_eint_setup(struct platform_device *platform_device) |
| { |
| int ret = 0; |
| u32 ints[4] = { 0 }; |
| struct device_node *node = NULL; |
| struct pinctrl_state *pins_default = NULL; |
| |
| accdet->pinctrl = devm_pinctrl_get(&platform_device->dev); |
| if (IS_ERR(accdet->pinctrl)) { |
| ret = PTR_ERR(accdet->pinctrl); |
| return ret; |
| } |
| |
| pins_default = pinctrl_lookup_state(accdet->pinctrl, "default"); |
| if (IS_ERR(pins_default)) |
| ret = PTR_ERR(pins_default); |
| |
| accdet->pins_eint = pinctrl_lookup_state(accdet->pinctrl, |
| "state_eint_as_int"); |
| if (IS_ERR(accdet->pins_eint)) { |
| ret = PTR_ERR(accdet->pins_eint); |
| return ret; |
| } |
| pinctrl_select_state(accdet->pinctrl, accdet->pins_eint); |
| |
| node = of_find_matching_node(node, accdet_of_match); |
| if (!node) |
| return -1; |
| |
| accdet->gpiopin = of_get_named_gpio(node, "deb-gpios", 0); |
| ret = of_property_read_u32(node, "debounce", |
| &accdet->gpio_hp_deb); |
| if (ret < 0) |
| return ret; |
| |
| gpio_set_debounce(accdet->gpiopin, accdet->gpio_hp_deb); |
| |
| accdet->gpioirq = irq_of_parse_and_map(node, 0); |
| ret = of_property_read_u32_array(node, "interrupts", ints, |
| ARRAY_SIZE(ints)); |
| if (ret) |
| return ret; |
| |
| accdet->accdet_eint_type = ints[1]; |
| ret = request_irq(accdet->gpioirq, ex_eint_handler, IRQF_TRIGGER_NONE, |
| "accdet-eint", NULL); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int accdet_get_dts_data(void) |
| { |
| int ret = 0; |
| struct device_node *node = NULL; |
| int pwm_deb[15] = {0}; |
| int three_key[4] = {0}; |
| u32 tmp = 0; |
| |
| node = of_find_matching_node(node, accdet_of_match); |
| if (!node) { |
| pr_notice("Error: %s can't find compatible dts node\n", |
| __func__); |
| return -1; |
| } |
| |
| /* moisture customized configuration */ |
| ret = of_property_read_u32(node, "moisture_detect_enable", |
| &accdet_dts.moisture_detect_enable); |
| if (ret) { |
| /* no moisture detection */ |
| accdet_dts.moisture_detect_enable = 0x0; |
| } |
| ret = of_property_read_u32(node, "eint_use_ext_res", |
| &accdet_dts.eint_use_ext_res); |
| if (ret) { |
| /* eint use internal resister */ |
| accdet_dts.eint_use_ext_res = 0x0; |
| } |
| ret = of_property_read_u32(node, "eint_detect_mode", |
| &accdet_dts.eint_detect_mode); |
| if (ret) { |
| /* eint detection mode equals to EINT 2.1 */ |
| accdet_dts.eint_detect_mode = 0x4; |
| } |
| if (accdet_dts.moisture_detect_enable == 0x0) { |
| /* eint detection mode equals to EINT 2.1 */ |
| accdet_dts.eint_detect_mode = 0x4; |
| } |
| ret = of_property_read_u32(node, "moisture_detect_mode", |
| &accdet_dts.moisture_detect_mode); |
| if (ret) { |
| /* no moisture detection */ |
| accdet_dts.moisture_detect_mode = 0x4; |
| } |
| ret = of_property_read_u32(node, "moisture_comp_vth", |
| &accdet_dts.moisture_comp_vth); |
| if (ret) { |
| /* no moisture detection */ |
| accdet_dts.moisture_comp_vth = 0x0; |
| } |
| ret = of_property_read_u32(node, "moisture_comp_vref2", |
| &accdet_dts.moisture_comp_vref2); |
| if (ret) { |
| /* no moisture detection */ |
| accdet_dts.moisture_comp_vref2 = 0x0; |
| } |
| ret = of_property_read_u32(node, "moisture_use_ext_res", |
| &accdet_dts.moisture_use_ext_res); |
| if (ret) { |
| /* no moisture detection */ |
| accdet_dts.moisture_use_ext_res = -1; |
| } |
| |
| /* if moisture detection enable, eint_detect_mode must equal to |
| * moisture_detect_mode |
| */ |
| if ((accdet_dts.moisture_detect_enable == 0x1) && |
| (accdet_dts.moisture_detect_mode != |
| accdet_dts.eint_detect_mode)) { |
| pr_notice("DTS setting error, eint mode != moisture mode\n\r"); |
| return -1; |
| } |
| |
| ret = of_property_read_u32(node, |
| "accdet-mic-vol", &accdet_dts.mic_vol); |
| if (ret) |
| accdet_dts.mic_vol = 8; |
| |
| ret = of_property_read_u32(node, "accdet-plugout-debounce", |
| &accdet_dts.plugout_deb); |
| if (ret) |
| accdet_dts.plugout_deb = 1; |
| |
| ret = of_property_read_u32(node, |
| "accdet-mic-mode", &accdet_dts.mic_mode); |
| if (ret) |
| accdet_dts.mic_mode = 2; |
| |
| ret = of_property_read_u32_array(node, "headset-mode-setting", pwm_deb, |
| ARRAY_SIZE(pwm_deb)); |
| /* debounce8(auxadc debounce) is default, needn't get from dts */ |
| if (!ret) |
| memcpy(&accdet_dts.pwm_deb, pwm_deb, sizeof(pwm_deb)); |
| |
| cust_pwm_deb = &accdet_dts.pwm_deb; |
| |
| ret = of_property_read_u32(node, "headset-eint-level-pol", |
| &accdet_dts.eint_pol); |
| if (ret) |
| accdet_dts.eint_pol = 8; |
| |
| pr_info("accdet mic_vol=%d, plugout_deb=%d mic_mode=%d eint_pol=%d\n", |
| accdet_dts.mic_vol, accdet_dts.plugout_deb, |
| accdet_dts.mic_mode, accdet_dts.eint_pol); |
| |
| ret = of_property_read_u32(node, |
| "headset-use-ap-eint", &tmp); |
| if (ret) |
| tmp = 0; |
| if (tmp == 0) |
| accdet->data->caps |= ACCDET_PMIC_EINT_IRQ; |
| else if (tmp == 1) |
| accdet->data->caps |= ACCDET_AP_GPIO_EINT; |
| |
| ret = of_property_read_u32(node, |
| "headset-eint-num", &tmp); |
| if (ret) |
| tmp = 0; |
| if (tmp == 0) |
| accdet->data->caps |= ACCDET_PMIC_EINT0; |
| else if (tmp == 1) |
| accdet->data->caps |= ACCDET_PMIC_EINT1; |
| else if (tmp == 2) |
| accdet->data->caps |= ACCDET_PMIC_BI_EINT; |
| |
| ret = of_property_read_u32(node, |
| "headset-eint-trig-mode", &tmp); |
| if (ret) |
| tmp = 0; |
| if (tmp == 0) |
| accdet->data->caps |= ACCDET_PMIC_GPIO_TRIG_EINT; |
| else if (tmp == 1) |
| accdet->data->caps |= ACCDET_PMIC_INVERTER_TRIG_EINT; |
| |
| ret = of_property_read_u32(node, |
| "headset-key-mode", &tmp); |
| if (ret) |
| tmp = 0; |
| if (tmp == 0) |
| accdet->data->caps |= ACCDET_THREE_KEY; |
| else if (tmp == 1) |
| accdet->data->caps |= ACCDET_FOUR_KEY; |
| else if (tmp == 2) |
| accdet->data->caps |= ACCDET_TRI_KEY_CDD; |
| |
| pr_info("accdet caps=%x\n", accdet->data->caps); |
| if (HAS_CAP(accdet->data->caps, ACCDET_FOUR_KEY)) { |
| int four_key[5]; |
| |
| ret = of_property_read_u32_array(node, |
| "headset-four-key-threshold", |
| four_key, ARRAY_SIZE(four_key)); |
| if (!ret) |
| memcpy(&accdet_dts.four_key, four_key+1, |
| sizeof(struct four_key_threshold)); |
| else { |
| pr_notice("accdet no 4-key-thrsh dts, use efuse\n"); |
| accdet_get_efuse_4key(); |
| } |
| } else { |
| if (HAS_CAP(accdet->data->caps, ACCDET_THREE_KEY)) { |
| ret = of_property_read_u32_array(node, |
| "headset-three-key-threshold", |
| three_key, ARRAY_SIZE(three_key)); |
| } else { |
| ret = of_property_read_u32_array(node, |
| "headset-three-key-threshold-CDD", three_key, |
| ARRAY_SIZE(three_key)); |
| } |
| if (!ret) |
| memcpy(&accdet_dts.three_key, three_key+1, |
| sizeof(struct three_key_threshold)); |
| } |
| dis_micbias_done = false; |
| |
| ret = of_property_read_u32(node, "moisture-water-r", &accdet->water_r); |
| if (ret) { |
| /* no moisture detection */ |
| accdet->water_r = 0x0; |
| } |
| ret = of_property_read_u32(node, "moisture_use_ext_res", |
| &accdet_dts.moisture_use_ext_res); |
| if (ret) { |
| /* no moisture detection */ |
| accdet_dts.moisture_use_ext_res = -1; |
| } |
| if (accdet_dts.moisture_use_ext_res == 0x1) { |
| of_property_read_u32(node, "moisture-external-r", |
| &accdet->moisture_ext_r); |
| pr_info("Moisture_EXT support water_r=%d, ext_r=%d\n", |
| accdet->water_r, accdet->moisture_ext_r); |
| } else if (accdet_dts.moisture_use_ext_res == 0x0) { |
| of_property_read_u32(node, "moisture-internal-r", |
| &accdet->moisture_int_r); |
| pr_info("Moisture_INT support water_r=%d, int_r=%d\n", |
| accdet->water_r, accdet->moisture_int_r); |
| } |
| ret = of_property_read_u32(node, "eint_use_ext_res", |
| &accdet_dts.eint_use_ext_res); |
| if (ret) { |
| /* eint use internal resister */ |
| accdet_dts.eint_use_ext_res = 0x0; |
| } |
| return 0; |
| } |
| |
| static void config_digital_init_by_mode(void) |
| { |
| /* enable eint cmpmem pwm */ |
| accdet_write(ACCDET_EINT_CMPMEN_PWM_THRESH_ADDR, |
| (accdet_dts.pwm_deb.eint_pwm_width << 4 | |
| accdet_dts.pwm_deb.eint_pwm_thresh)); |
| /* DA signal stable */ |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| accdet_write(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT0_STABLE_VAL); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| accdet_write(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT1_STABLE_VAL); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| accdet_write(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT0_STABLE_VAL); |
| accdet_write(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT1_STABLE_VAL); |
| } |
| if (accdet_dts.moisture_detect_mode == 0x5) { |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| /* clr DA stable signal */ |
| accdet_clear_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT0_CEN_STABLE_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* clr DA stable signal */ |
| accdet_clear_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT1_CEN_STABLE_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* clr DA stable signal */ |
| accdet_clear_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT0_CEN_STABLE_SFT); |
| /* clr DA stable signal */ |
| accdet_clear_bit(ACCDET_DA_STABLE_ADDR, |
| ACCDET_EINT1_CEN_STABLE_SFT); |
| } |
| } |
| /* after receive n+1 number, interrupt issued. now is 2 times */ |
| accdet_update_bit(ACCDET_EINT_M_PLUG_IN_NUM_ADDR, |
| ACCDET_EINT_M_PLUG_IN_NUM_SFT); |
| /* setting HW mode, enable digital fast discharge |
| * if use EINT0 & EINT1 detection, please modify |
| * ACCDET_HWMODE_EN_ADDR[2:1] |
| */ |
| if (accdet_dts.moisture_detect_mode == 0x1) { |
| /* disable moisture detection function */ |
| accdet_clear_bit(ACCDET_EINT_M_DETECT_EN_ADDR, |
| ACCDET_EINT_M_DETECT_EN_SFT); |
| /* wk1, disable hwmode */ |
| accdet_write(ACCDET_HWMODE_EN_ADDR, 0x0); |
| /* enable inverter detection */ |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| accdet_update_bit(ACCDET_EINT0_SW_EN_ADDR, |
| ACCDET_EINT0_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| accdet_update_bit(ACCDET_EINT1_SW_EN_ADDR, |
| ACCDET_EINT1_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| accdet_update_bit(ACCDET_EINT0_SW_EN_ADDR, |
| ACCDET_EINT0_SW_EN_SFT); |
| accdet_update_bit(ACCDET_EINT1_SW_EN_ADDR, |
| ACCDET_EINT1_SW_EN_SFT); |
| } |
| } else if (accdet_dts.moisture_detect_mode == 0x2) { |
| /* disable moisture detection function */ |
| accdet_clear_bit(ACCDET_EINT_M_DETECT_EN_ADDR, |
| ACCDET_EINT_M_DETECT_EN_SFT); |
| /* wk1, disable hwmode */ |
| accdet_write(ACCDET_HWMODE_EN_ADDR, 0x100); |
| } else if (accdet_dts.moisture_detect_mode == 0x3) { |
| /* disable moisture detection function */ |
| accdet_clear_bit(ACCDET_EINT_M_DETECT_EN_ADDR, |
| ACCDET_EINT_M_DETECT_EN_SFT); |
| accdet_update_bit(ACCDET_EINT_CMPMOUT_SEL_ADDR, |
| ACCDET_EINT_CMPMOUT_SEL_SFT); |
| accdet_update_bit(ACCDET_EINT_CMPMEN_SEL_ADDR, |
| ACCDET_EINT_CMPMEN_SEL_SFT); |
| /* wk1, disable hwmode */ |
| accdet_write(ACCDET_HWMODE_EN_ADDR, 0x100); |
| } else if (accdet_dts.moisture_detect_mode == 0x4) { |
| /* enable moisture detection function */ |
| accdet_update_bit(ACCDET_EINT_M_DETECT_EN_ADDR, |
| ACCDET_EINT_M_DETECT_EN_SFT); |
| /* wk1, disable hwmode */ |
| accdet_write(ACCDET_HWMODE_EN_ADDR, 0x100); |
| } else if (accdet_dts.moisture_detect_mode == 0x5) { |
| /* blocking CTURBO */ |
| accdet_update_bit(ACCDET_EINT_CTURBO_SEL_ADDR, |
| ACCDET_EINT_CTURBO_SEL_SFT); |
| /* enable moisture detection function */ |
| accdet_update_bit(ACCDET_EINT_M_DETECT_EN_ADDR, |
| ACCDET_EINT_M_DETECT_EN_SFT); |
| |
| /* wk1, disable hwmode */ |
| accdet_write(ACCDET_HWMODE_EN_ADDR, 0x500); |
| accdet_update_bit(RG_HPLOUTPUTSTBENH_VAUDP32_ADDR, |
| RG_HPLOUTPUTSTBENH_VAUDP32_SFT); |
| accdet_update_bit(RG_HPROUTPUTSTBENH_VAUDP32_ADDR, |
| RG_HPROUTPUTSTBENH_VAUDP32_SFT); |
| } else { |
| /* wk1, disable hwmode */ |
| accdet_write(ACCDET_HWMODE_EN_ADDR, 0x100); |
| } |
| |
| if (accdet_dts.moisture_detect_enable == 0) { |
| pr_info("%s() disable digital moisture.\n", __func__); |
| /* disable moisture detection function */ |
| accdet_clear_bit(ACCDET_EINT_M_DETECT_EN_ADDR, |
| ACCDET_EINT_M_DETECT_EN_SFT); |
| } |
| |
| /* enable PWM */ |
| accdet_write(ACCDET_CMP_PWM_EN_ADDR, 0x67); |
| /* enable inverter detection */ |
| if (accdet_dts.eint_detect_mode == 0x1) { |
| /* disable inverter detection */ |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| accdet_clear_bit(ACCDET_EINT0_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT0_INVERTER_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| accdet_clear_bit(ACCDET_EINT1_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT1_INVERTER_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| accdet_clear_bit(ACCDET_EINT0_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT0_INVERTER_SW_EN_SFT); |
| accdet_clear_bit(ACCDET_EINT1_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT1_INVERTER_SW_EN_SFT); |
| } |
| } else { |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| accdet_update_bit(ACCDET_EINT0_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT0_INVERTER_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| accdet_update_bit(ACCDET_EINT1_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT1_INVERTER_SW_EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| accdet_update_bit(ACCDET_EINT0_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT0_INVERTER_SW_EN_SFT); |
| accdet_update_bit(ACCDET_EINT1_INVERTER_SW_EN_ADDR, |
| ACCDET_EINT1_INVERTER_SW_EN_SFT); |
| |
| } |
| } |
| } |
| |
| static void config_analog_moisture_init_by_mode(void) |
| { |
| if (accdet_dts.moisture_detect_mode == 0x1) { |
| config_moisture_detect_1_0(); |
| } else if ((accdet_dts.moisture_detect_mode == 0x2) || |
| (accdet_dts.moisture_detect_mode == 0x3)) { |
| config_moisture_detect_1_1(); |
| } else if (accdet_dts.moisture_detect_mode == 0x4) { |
| config_moisture_detect_2_1(); |
| } else if (accdet_dts.moisture_detect_mode == 0x5) { |
| config_moisture_detect_2_1_1(); |
| } |
| } |
| |
| static void config_eint_init_by_mode(void) |
| { |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT0)) { |
| accdet_update_bit(RG_EINT0EN_ADDR, |
| RG_EINT0EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT1)) { |
| accdet_update_bit(RG_EINT1EN_ADDR, |
| RG_EINT1EN_SFT); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_BI_EINT)) { |
| accdet_update_bit(RG_EINT0EN_ADDR, |
| RG_EINT0EN_SFT); |
| accdet_update_bit(RG_EINT1EN_ADDR, |
| RG_EINT1EN_SFT); |
| } |
| /* ESD switches on */ |
| accdet_update_bit(RG_ACCDETSPARE_ADDR, 8); |
| /* before playback, set NCP pull low before nagative voltage */ |
| accdet_update_bit(RG_NCP_PDDIS_EN_ADDR, |
| RG_NCP_PDDIS_EN_SFT); |
| |
| if ((accdet_dts.eint_detect_mode == 0x1) || |
| (accdet_dts.eint_detect_mode == 0x2) || |
| (accdet_dts.eint_detect_mode == 0x3)) { |
| |
| if (accdet_dts.eint_use_ext_res == 0x1) { |
| /* select VTH to 2v and 500k, use external resitance */ |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT0)) { |
| /* disable RG_EINT0CONFIGACCDET */ |
| accdet_clear_bit(RG_EINT0CONFIGACCDET_ADDR, |
| RG_EINT0CONFIGACCDET_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* disable RG_EINT1CONFIGACCDET */ |
| accdet_clear_bit(RG_EINT1CONFIGACCDET_ADDR, |
| RG_EINT1CONFIGACCDET_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* disable RG_EINT0CONFIGACCDET */ |
| accdet_clear_bit(RG_EINT0CONFIGACCDET_ADDR, |
| RG_EINT0CONFIGACCDET_SFT); |
| /* disable RG_EINT1CONFIGACCDET */ |
| accdet_clear_bit(RG_EINT1CONFIGACCDET_ADDR, |
| RG_EINT1CONFIGACCDET_SFT); |
| } |
| } else { |
| /* select VTH to 2v and 500k, use internal resitance */ |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT0)) { |
| /* enable RG_EINT0CONFIGACCDET */ |
| accdet_update_bit(RG_EINT0CONFIGACCDET_ADDR, |
| RG_EINT0CONFIGACCDET_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* enable RG_EINT1CONFIGACCDET */ |
| accdet_update_bit(RG_EINT1CONFIGACCDET_ADDR, |
| RG_EINT1CONFIGACCDET_SFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* enable RG_EINT0CONFIGACCDET */ |
| accdet_update_bit(RG_EINT0CONFIGACCDET_ADDR, |
| RG_EINT0CONFIGACCDET_SFT); |
| /* enable RG_EINT1CONFIGACCDET */ |
| accdet_update_bit(RG_EINT1CONFIGACCDET_ADDR, |
| RG_EINT1CONFIGACCDET_SFT); |
| } |
| } |
| } else if (accdet_dts.eint_detect_mode == 0x4) { |
| /* do nothing */ |
| } else if (accdet_dts.eint_detect_mode == 0x5) { |
| /* do nothing */ |
| } |
| |
| if (accdet_dts.eint_detect_mode != 0x1) { |
| /* current detect set 0.25uA */ |
| accdet_update_bits(RG_ACCDETSPARE_ADDR, |
| RG_ACCDETSPARE_SFT, |
| 0x3, 0x3); |
| } |
| /* new customized parameter */ |
| if ((accdet_dts.eint_use_ext_res == 0x2) || |
| (accdet_dts.eint_use_ext_res == 0x4) || |
| (accdet_dts.eint_use_ext_res == 0x5)) { |
| /* select VTH to 2v */ |
| accdet_update_bits(RG_EINTCOMPVTH_ADDR, |
| RG_EINTCOMPVTH_SFT, RG_EINTCOMPVTH_MASK, |
| 0x2); |
| } |
| } |
| |
| static void accdet_init_once(void) |
| { |
| unsigned int reg = 0; |
| |
| /* reset the accdet unit */ |
| accdet_update_bit(RG_ACCDET_RST_ADDR, |
| RG_ACCDET_RST_SFT); |
| accdet_clear_bit(RG_ACCDET_RST_ADDR, |
| RG_ACCDET_RST_SFT); |
| |
| /* clear high micbias1 voltage setting */ |
| accdet_clear_bits(RG_AUDPWDBMICBIAS1_ADDR, |
| RG_AUDMICBIAS1HVEN_SFT, 0x3, 0x3); |
| /* clear micbias1 voltage */ |
| accdet_clear_bits(RG_AUDPWDBMICBIAS1_ADDR, |
| RG_AUDMICBIAS1VREF_SFT, 0x7, 0x7); |
| |
| /* init pwm frequency, duty & rise/falling delay */ |
| accdet_write(ACCDET_PWM_WIDTH_ADDR, |
| REGISTER_VAL(cust_pwm_deb->pwm_width)); |
| accdet_write(ACCDET_PWM_THRESH_ADDR, |
| REGISTER_VAL(cust_pwm_deb->pwm_thresh)); |
| accdet_write(ACCDET_RISE_DELAY_ADDR, |
| (cust_pwm_deb->fall_delay << 15 | cust_pwm_deb->rise_delay)); |
| |
| /* config micbias voltage, micbias1 vref is only controlled by accdet |
| * if we need 2.8V, config [12:13] |
| */ |
| reg = accdet_read(RG_AUDPWDBMICBIAS1_ADDR); |
| if (accdet_dts.mic_vol <= 7) { |
| /* micbias1 <= 2.7V */ |
| accdet_write(RG_AUDPWDBMICBIAS1_ADDR, |
| reg | (accdet_dts.mic_vol<<RG_AUDMICBIAS1VREF_SFT) | |
| RG_AUDMICBIAS1LOWPEN_MASK_SFT); |
| } else if (accdet_dts.mic_vol == 8) { |
| /* micbias1 = 2.8v */ |
| accdet_write(RG_AUDPWDBMICBIAS1_ADDR, |
| reg | (3<<RG_AUDMICBIAS1HVEN_SFT) | |
| RG_AUDMICBIAS1LOWPEN_MASK_SFT); |
| } else if (accdet_dts.mic_vol == 9) { |
| /* micbias1 = 2.85v */ |
| accdet_write(RG_AUDPWDBMICBIAS1_ADDR, |
| reg | (1<<RG_AUDMICBIAS1HVEN_SFT) | |
| RG_AUDMICBIAS1LOWPEN_MASK_SFT); |
| } |
| /* mic mode setting */ |
| reg = accdet_read(RG_AUDACCDETMICBIAS0PULLLOW_ADDR); |
| if (accdet_dts.mic_mode == HEADSET_MODE_1) { |
| /* ACC mode*/ |
| accdet_write(RG_AUDACCDETMICBIAS0PULLLOW_ADDR, |
| reg | RG_ACCDET_MODE_ANA11_MODE1); |
| /* enable analog fast discharge */ |
| accdet_update_bit(RG_ANALOGFDEN_ADDR, |
| RG_ANALOGFDEN_SFT); |
| accdet_update_bits(RG_ACCDETSPARE_ADDR, 11, 0x3, 0x3); |
| } else if (accdet_dts.mic_mode == HEADSET_MODE_2) { |
| /* DCC mode Low cost mode without internal bias*/ |
| accdet_write(RG_AUDACCDETMICBIAS0PULLLOW_ADDR, |
| reg | RG_ACCDET_MODE_ANA11_MODE2); |
| /* enable analog fast discharge */ |
| accdet_update_bits(RG_ANALOGFDEN_ADDR, |
| RG_ANALOGFDEN_SFT, 0x3, 0x3); |
| } else if (accdet_dts.mic_mode == HEADSET_MODE_6) { |
| /* DCC mode Low cost mode with internal bias, |
| * bit8 = 1 to use internal bias |
| */ |
| accdet_write(RG_AUDACCDETMICBIAS0PULLLOW_ADDR, |
| reg | RG_ACCDET_MODE_ANA11_MODE6); |
| accdet_update_bit(RG_AUDPWDBMICBIAS1_ADDR, |
| RG_AUDMICBIAS1DCSW1PEN_SFT); |
| /* enable analog fast discharge */ |
| accdet_update_bits(RG_ANALOGFDEN_ADDR, |
| RG_ANALOGFDEN_SFT, 0x3, 0x3); |
| } |
| |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT_IRQ)) { |
| config_eint_init_by_mode(); |
| if (accdet_dts.moisture_detect_enable == 1) { |
| pr_info("%s() set analog moisture.\n", __func__); |
| config_analog_moisture_init_by_mode(); |
| } |
| config_digital_init_by_mode(); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_AP_GPIO_EINT)) { |
| /* set pull low pads and DCC mode */ |
| accdet_write(RG_AUDACCDETMICBIAS0PULLLOW_ADDR, |
| 0x8F); |
| /* disconnect configaccdet */ |
| accdet_write(RG_EINT1CONFIGACCDET_ADDR, |
| 0x0); |
| /* disable eint comparator */ |
| accdet_write(RG_EINT0CMPEN_ADDR, 0x0); |
| /* enable PWM */ |
| accdet_write(ACCDET_CMP_PWM_EN_ADDR, 0x7); |
| /* enable accdet sw mode */ |
| accdet_write(ACCDET_HWMODE_EN_ADDR, 0x0); |
| /* set DA signal to stable */ |
| accdet_write(ACCDET_DA_STABLE_ADDR, 0x1); |
| /* disable eint/inverter/sw_en */ |
| accdet_write(ACCDET_SW_EN_ADDR, 0x0); |
| } |
| pr_info("%s done!\n", __func__); |
| } |
| |
| static void accdet_init_debounce(void) |
| { |
| /* set debounce to 1ms */ |
| accdet_set_debounce(eint_state000, |
| accdet_dts.pwm_deb.eint_debounce0); |
| /* set debounce to 128ms */ |
| accdet_set_debounce(eint_state011, |
| accdet_dts.pwm_deb.eint_debounce3); |
| } |
| |
| static inline void accdet_init(void) |
| { |
| /* set and clear initial bit every eint interrutp */ |
| accdet_update_bit(ACCDET_SEQ_INIT_ADDR, |
| ACCDET_SEQ_INIT_SFT); |
| usleep_range(2000, 3000); |
| accdet_clear_bit(ACCDET_SEQ_INIT_ADDR, |
| ACCDET_SEQ_INIT_SFT); |
| usleep_range(1000, 1500); |
| /* init the debounce time (debounce/32768)sec */ |
| accdet_set_debounce(accdet_state000, cust_pwm_deb->debounce0); |
| accdet_set_debounce(accdet_state001, cust_pwm_deb->debounce1); |
| accdet_set_debounce(accdet_state011, cust_pwm_deb->debounce3); |
| /* auxadc:2ms */ |
| accdet_set_debounce(accdet_auxadc, cust_pwm_deb->debounce4); |
| if (accdet_dts.moisture_detect_enable == 0x1) { |
| /* eint_state001 can be configured, less than 2ms */ |
| accdet_set_debounce(eint_state001, |
| accdet_dts.pwm_deb.eint_debounce1); |
| accdet_set_debounce(eint_state010, |
| accdet_dts.pwm_deb.eint_debounce2); |
| } else { |
| accdet_set_debounce(eint_state001, |
| accdet_dts.pwm_deb.eint_debounce1); |
| } |
| accdet_set_debounce(eint_inverter_state000, |
| accdet_dts.pwm_deb.eint_inverter_debounce); |
| |
| pr_info("%s() done.\n", __func__); |
| } |
| |
| /* late init for DC trim, and this API Will be called by audio */ |
| void accdet_late_init(unsigned long data) |
| { |
| pr_info("%s() now init accdet!\n", __func__); |
| if (atomic_cmpxchg(&accdet_first, 1, 0)) { |
| del_timer_sync(&accdet_init_timer); |
| accdet_init(); |
| accdet_init_debounce(); |
| accdet_init_once(); |
| } else |
| pr_info("%s inited dts fail\n", __func__); |
| } |
| EXPORT_SYMBOL(accdet_late_init); |
| |
| void accdet_modify_vref_volt(void) |
| { |
| } |
| EXPORT_SYMBOL(accdet_modify_vref_volt); |
| |
| static void accdet_modify_vref_volt_self(void) |
| { |
| u32 cur_AB, eintID; |
| |
| if (accdet_dts.moisture_detect_mode == 0x5) { |
| /* make sure seq is disable micbias then connect vref2 */ |
| |
| /* check EINT0 status, if plug out, |
| * not need to disable accdet here |
| */ |
| eintID = accdet_read_bits(ACCDET_EINT0_MEM_IN_ADDR, |
| ACCDET_EINT0_MEM_IN_SFT, |
| ACCDET_EINT0_MEM_IN_MASK); |
| if (eintID == M_PLUG_OUT) { |
| pr_info("%s Plug-out, no dis micbias\n", __func__); |
| return; |
| } |
| cur_AB = accdet_read(ACCDET_MEM_IN_ADDR) >> |
| ACCDET_STATE_MEM_IN_OFFSET; |
| cur_AB = cur_AB & ACCDET_STATE_AB_MASK; |
| |
| /* if 3pole disable accdet |
| * if <20k + 4pole, disable accdet will disable accdet |
| * plug out interrupt. The behavior will same as 3pole |
| */ |
| if (accdet->cable_type == HEADSET_MIC) { |
| /* do nothing */ |
| } else if ((accdet->cable_type == HEADSET_NO_MIC) || |
| (cur_AB == ACCDET_STATE_AB_00) || |
| (cur_AB == ACCDET_STATE_AB_11)) { |
| /* disable accdet_sw_en=0 |
| * disable accdet_hwmode_en=0 |
| */ |
| accdet_clear_bit(ACCDET_SW_EN_ADDR, |
| ACCDET_SW_EN_SFT); |
| disable_accdet(); |
| pr_info("%s MICBIAS:Disabled AB:0x%x c_type:0x%x\n", |
| __func__, cur_AB, accdet->cable_type); |
| dis_micbias_done = true; |
| } |
| /* disable comp1 delay window */ |
| accdet_update_bit(RG_EINT0NOHYS_ADDR, |
| RG_EINT0NOHYS_SFT); |
| /* connect VREF2 to EINT0CMP */ |
| accdet_update_bits(RG_EINTCOMPVTH_ADDR, |
| RG_EINTCOMPVTH_SFT, 0x3, 0x3); |
| pr_info("%s [0x%x]=0x%x [0x%x]=0x%x\n", __func__, |
| RG_EINT0NOHYS_ADDR, |
| accdet_read(RG_EINT0NOHYS_ADDR), |
| RG_EINTCOMPVTH_ADDR, |
| accdet_read(RG_EINTCOMPVTH_ADDR)); |
| } |
| } |
| |
| static void delay_init_work_callback(struct work_struct *work) |
| { |
| accdet_init(); |
| accdet_init_debounce(); |
| accdet_init_once(); |
| pr_info("%s() done\n", __func__); |
| } |
| |
| static void delay_init_timerhandler(struct timer_list *t) |
| { |
| int ret = 0; |
| |
| ret = queue_work(accdet->delay_init_workqueue, |
| &accdet->delay_init_work); |
| if (!ret) |
| pr_notice("Error: %s (%d)\n", __func__, ret); |
| } |
| |
| int mt6359p_accdet_init(struct snd_soc_component *component, |
| struct snd_soc_card *card) |
| { |
| int ret; |
| struct mt63xx_accdet_data *priv = |
| snd_soc_card_get_drvdata(component->card); |
| |
| /* Enable Headset and 4 Buttons Jack detection */ |
| ret = snd_soc_card_jack_new(card, |
| "Headset Jack", |
| SND_JACK_HEADSET | |
| SND_JACK_LINEOUT | |
| SND_JACK_MECHANICAL, |
| &priv->jack, |
| NULL, 0); |
| if (ret) { |
| pr_err("Property 'mediatek,soc-accdet' missing/invalid\n"); |
| return ret; |
| } |
| |
| accdet->jack.jack->input_dev->id.bustype = BUS_HOST; |
| snd_jack_set_key(accdet->jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); |
| snd_jack_set_key(accdet->jack.jack, SND_JACK_BTN_1, KEY_VOLUMEDOWN); |
| snd_jack_set_key(accdet->jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP); |
| snd_jack_set_key(accdet->jack.jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(mt6359p_accdet_init); |
| |
| int mt6359p_accdet_set_drvdata(struct snd_soc_card *card) |
| { |
| snd_soc_card_set_drvdata(card, accdet); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(mt6359p_accdet_set_drvdata); |
| |
| static int accdet_probe(struct platform_device *pdev) |
| { |
| int ret = 0; |
| struct resource *res; |
| struct mt6397_chip *mt6397_chip = dev_get_drvdata(pdev->dev.parent); |
| const struct of_device_id *of_id = |
| of_match_device(accdet_of_match, &pdev->dev); |
| if (!of_id) { |
| dev_dbg(&pdev->dev, "Error: No device match found\n"); |
| return -ENODEV; |
| } |
| |
| accdet = devm_kzalloc(&pdev->dev, sizeof(*accdet), GFP_KERNEL); |
| if (!accdet) |
| return -ENOMEM; |
| |
| accdet->data = (struct accdet_priv *)of_id->data; |
| accdet->pdev = pdev; |
| |
| /* parse dts attributes */ |
| ret = accdet_get_dts_data(); |
| if (ret) { |
| dev_dbg(&pdev->dev, "Error: Get dts data failed (%d)\n", |
| ret); |
| return ret; |
| } |
| /* init lock */ |
| accdet->wake_lock = wakeup_source_register(NULL, "accdet_wake_lock"); |
| if (!accdet->wake_lock) |
| return -ENOMEM; |
| accdet->timer_lock = wakeup_source_register(NULL, "accdet_timer_lock"); |
| if (!accdet->timer_lock) |
| return -ENOMEM; |
| mutex_init(&accdet->res_lock); |
| |
| platform_set_drvdata(pdev, accdet); |
| /* Important. must to register */ |
| ret = devm_snd_soc_register_component(&pdev->dev, &accdet_soc_driver, |
| NULL, 0); |
| |
| if (ret) { |
| dev_err(&pdev->dev, |
| "Property 'mediatek,soc-accdet' missing/invalid\n"); |
| return ret; |
| } |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| accdet->regmap = mt6397_chip->regmap; |
| accdet->dev = &pdev->dev; |
| |
| /* get pmic auxadc iio channel handler */ |
| accdet->accdet_auxadc = devm_iio_channel_get(&pdev->dev, |
| "pmic_accdet"); |
| ret = PTR_ERR_OR_ZERO(accdet->accdet_auxadc); |
| if (ret) { |
| if (ret != -EPROBE_DEFER) |
| dev_dbg(&pdev->dev, "Error: Get iio ch failed (%d)\n", |
| ret); |
| return ret; |
| } |
| |
| /* get pmic efuse handler */ |
| accdet->accdet_efuse = devm_nvmem_device_get(&pdev->dev, |
| "mt63xx-accdet-efuse"); |
| ret = PTR_ERR_OR_ZERO(accdet->accdet_efuse); |
| if (ret) { |
| if (ret != -EPROBE_DEFER) |
| dev_dbg(&pdev->dev, "Error: Get efuse failed (%d)\n", |
| ret); |
| return ret; |
| } |
| |
| accdet_get_efuse(); |
| |
| /* register pmic interrupt */ |
| accdet->accdet_irq = platform_get_irq(pdev, 0); |
| if (accdet->accdet_irq < 0) { |
| dev_dbg(&pdev->dev, |
| "Error: Get accdet irq failed (%d)\n", |
| accdet->accdet_irq); |
| return accdet->accdet_irq; |
| } |
| ret = devm_request_threaded_irq(&pdev->dev, accdet->accdet_irq, |
| NULL, mtk_accdet_irq_handler_thread, |
| IRQF_TRIGGER_HIGH | IRQF_ONESHOT, |
| "ACCDET_IRQ", accdet); |
| if (ret) { |
| dev_dbg(&pdev->dev, |
| "Error: Get thread irq request failed (%d) (%d)\n", |
| accdet->accdet_irq, ret); |
| return ret; |
| } |
| |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT0)) { |
| accdet->accdet_eint0 = platform_get_irq(pdev, 1); |
| if (accdet->accdet_eint0 < 0) { |
| dev_dbg(&pdev->dev, |
| "Error: Get eint0 irq failed (%d)\n", |
| accdet->accdet_eint0); |
| return accdet->accdet_eint0; |
| } |
| ret = devm_request_threaded_irq(&pdev->dev, |
| accdet->accdet_eint0, |
| NULL, mtk_accdet_irq_handler_thread, |
| IRQF_TRIGGER_HIGH | IRQF_ONESHOT, |
| "ACCDET_EINT0", accdet); |
| if (ret) { |
| dev_dbg(&pdev->dev, |
| "Error: Get eint0 irq request failed (%d)\n", |
| ret); |
| return ret; |
| } |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT1)) { |
| accdet->accdet_eint1 = platform_get_irq(pdev, 2); |
| if (accdet->accdet_eint1 < 0) { |
| dev_dbg(&pdev->dev, |
| "Error: Get eint1 irq failed (%d)\n", |
| accdet->accdet_eint1); |
| return accdet->accdet_eint1; |
| } |
| ret = devm_request_threaded_irq(&pdev->dev, |
| accdet->accdet_eint1, |
| NULL, mtk_accdet_irq_handler_thread, |
| IRQF_TRIGGER_HIGH | IRQF_ONESHOT, |
| "ACCDET_EINT1", accdet); |
| if (ret) { |
| dev_dbg(&pdev->dev, |
| "Error: Get eint1 irq request failed (%d)\n", |
| ret); |
| return ret; |
| } |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_BI_EINT)) { |
| accdet->accdet_eint0 = platform_get_irq(pdev, 1); |
| if (accdet->accdet_eint0 < 0) { |
| dev_dbg(&pdev->dev, |
| "Error: Get eint0 irq failed (%d)\n", |
| accdet->accdet_eint0); |
| return accdet->accdet_eint0; |
| } |
| ret = devm_request_threaded_irq(&pdev->dev, |
| accdet->accdet_eint0, |
| NULL, mtk_accdet_irq_handler_thread, |
| IRQF_TRIGGER_HIGH | IRQF_ONESHOT, |
| "ACCDET_EINT0", accdet); |
| if (ret) { |
| dev_dbg(&pdev->dev, |
| "Error: Get eint0 irq request failed (%d)\n", |
| ret); |
| return ret; |
| } |
| accdet->accdet_eint1 = platform_get_irq(pdev, 2); |
| if (accdet->accdet_eint1 < 0) { |
| dev_dbg(&pdev->dev, |
| "Error: Get eint1 irq failed (%d)\n", |
| accdet->accdet_eint1); |
| return accdet->accdet_eint1; |
| } |
| ret = devm_request_threaded_irq(&pdev->dev, |
| accdet->accdet_eint1, |
| NULL, mtk_accdet_irq_handler_thread, |
| IRQF_TRIGGER_HIGH | IRQF_ONESHOT, |
| "ACCDET_EINT1", accdet); |
| if (ret) { |
| dev_dbg(&pdev->dev, |
| "Error: Get eint1 irq request failed (%d)\n", |
| ret); |
| return ret; |
| } |
| } |
| |
| /* register char device number, Create normal device for auido use */ |
| ret = alloc_chrdev_region(&accdet->accdet_devno, 0, 1, ACCDET_DEVNAME); |
| if (ret) |
| goto err_chrdevregion; |
| |
| /* create class in sysfs, "sys/class/", so udev in userspace can create |
| * device node, when device_create is called |
| */ |
| accdet->accdet_class = class_create(THIS_MODULE, ACCDET_DEVNAME); |
| if (!accdet->accdet_class) { |
| dev_dbg(&pdev->dev, |
| "Error: Create class failed (%d)\n", ret); |
| ret = -1; |
| } |
| |
| /* setup timer */ |
| timer_setup(&micbias_timer, dis_micbias_timerhandler, 0); |
| micbias_timer.expires = jiffies + MICBIAS_DISABLE_TIMER; |
| timer_setup(&accdet_init_timer, delay_init_timerhandler, 0); |
| accdet_init_timer.expires = jiffies + ACCDET_INIT_WAIT_TIMER; |
| |
| /* Create workqueue */ |
| accdet->delay_init_workqueue = |
| create_singlethread_workqueue("delay_init"); |
| INIT_WORK(&accdet->delay_init_work, delay_init_work_callback); |
| if (!accdet->delay_init_workqueue) { |
| dev_dbg(&pdev->dev, "Error: Create dinit workqueue failed\n"); |
| ret = -1; |
| goto err_device_create; |
| } |
| accdet->accdet_workqueue = create_singlethread_workqueue("accdet"); |
| INIT_WORK(&accdet->accdet_work, accdet_work_callback); |
| if (!accdet->accdet_workqueue) { |
| dev_dbg(&pdev->dev, "Error: Create accdet workqueue failed\n"); |
| ret = -1; |
| goto err_device_create; |
| } |
| |
| accdet->dis_micbias_workqueue = |
| create_singlethread_workqueue("dismicQueue"); |
| INIT_WORK(&accdet->dis_micbias_work, dis_micbias_work_callback); |
| if (!accdet->dis_micbias_workqueue) { |
| dev_dbg(&pdev->dev, "Error: Create dismic workqueue failed\n"); |
| ret = -1; |
| goto err; |
| } |
| accdet->eint_workqueue = create_singlethread_workqueue("accdet_eint"); |
| INIT_WORK(&accdet->eint_work, eint_work_callback); |
| if (!accdet->eint_workqueue) { |
| dev_dbg(&pdev->dev, "Error: Create eint workqueue failed\n"); |
| ret = -1; |
| goto err_create_workqueue; |
| } |
| if (HAS_CAP(accdet->data->caps, ACCDET_AP_GPIO_EINT)) { |
| accdet->accdet_eint_type = IRQ_TYPE_LEVEL_LOW; |
| ret = ext_eint_setup(pdev); |
| if (ret) |
| destroy_workqueue(accdet->eint_workqueue); |
| } |
| |
| ret = accdet_create_attr(&accdet_driver.driver); |
| if (ret) { |
| pr_notice("%s create_attr fail, ret = %d\n", __func__, ret); |
| goto err_create_workqueue; |
| } |
| atomic_set(&accdet_first, 1); |
| mod_timer(&accdet_init_timer, (jiffies + ACCDET_INIT_WAIT_TIMER)); |
| |
| return 0; |
| |
| err_create_workqueue: |
| destroy_workqueue(accdet->dis_micbias_workqueue); |
| err: |
| destroy_workqueue(accdet->accdet_workqueue); |
| destroy_workqueue(accdet->delay_init_workqueue); |
| err_device_create: |
| class_destroy(accdet->accdet_class); |
| err_chrdevregion: |
| pr_notice("%s error. now exit.!\n", __func__); |
| return ret; |
| } |
| |
| static int accdet_remove(struct platform_device *pdev) |
| { |
| destroy_workqueue(accdet->eint_workqueue); |
| destroy_workqueue(accdet->dis_micbias_workqueue); |
| destroy_workqueue(accdet->accdet_workqueue); |
| destroy_workqueue(accdet->delay_init_workqueue); |
| class_destroy(accdet->accdet_class); |
| unregister_chrdev_region(accdet->accdet_devno, 1); |
| devm_kfree(&pdev->dev, accdet); |
| return 0; |
| } |
| |
| static long mt_accdet_unlocked_ioctl(struct file *file, unsigned int cmd, |
| unsigned long arg) |
| { |
| switch (cmd) { |
| case GET_BUTTON_STATUS: |
| return accdet->button_status; |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| static const struct file_operations accdet_fops = { |
| .owner = THIS_MODULE, |
| .unlocked_ioctl = mt_accdet_unlocked_ioctl, |
| }; |
| |
| const struct file_operations *accdet_get_fops(void) |
| { |
| return &accdet_fops; |
| } |
| |
| static struct platform_driver accdet_driver = { |
| .probe = accdet_probe, |
| .remove = accdet_remove, |
| .driver = { |
| .name = "pmic-codec-accdet", |
| .of_match_table = accdet_of_match, |
| }, |
| }; |
| |
| static int __init accdet_soc_init(void) |
| { |
| int ret = 0; |
| |
| ret = platform_driver_register(&accdet_driver); |
| if (ret) |
| return -ENODEV; |
| return 0; |
| } |
| static void __exit accdet_soc_exit(void) |
| { |
| platform_driver_unregister(&accdet_driver); |
| } |
| module_init(accdet_soc_init); |
| module_exit(accdet_soc_exit); |
| |
| /* Module information */ |
| MODULE_DESCRIPTION("MT6359 ALSA SoC accdet driver"); |
| MODULE_AUTHOR("Argus Lin <argus.lin@mediatek.com>"); |
| MODULE_LICENSE("GPL v2"); |