| // 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/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/mt6359/registers.h> |
| #include <linux/mfd/mt6397/core.h> |
| #include <linux/mfd/mt6359/core.h> |
| #include "mt6359-accdet.h" |
| #include "mt6359.h" |
| /* grobal variable definitions */ |
| #define REGISTER_VAL(x) (x - 1) |
| #define HAS_CAP(_c, _x) (((_c) & (_x)) == (_x)) |
| #define ACCDET_HW_FAST_DISCHRAGE BIT(0) |
| #define ACCDET_PMIC_EINT_IRQ 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_EINT BIT(5) |
| #define ACCDET_PMIC_INVERTER_EINT BIT(6) |
| #define ACCDET_AP_GPIO_EINT BIT(7) |
| #define ACCDET_THREE_KEY BIT(8) |
| #define ACCDET_FOUR_KEY BIT(9) |
| #define ACCDET_TRI_KEY_CDD BIT(10) |
| |
| /* Used to let accdet know if the pin has been fully plugged-in */ |
| #define EINT_PLUG_OUT (0) |
| #define EINT_PLUG_IN (1) |
| |
| /* defines for codec accdet registers */ |
| enum accdet_regs { |
| TOP_TOP0_ANA_ID, |
| TOP_SWCID, |
| TOP_RG_RTC32K_CK_PDN, |
| TOP_INT_STATUS_AUD_TOP, |
| PLT_CPU_INT_STA, |
| LDO_RG_LDO_VUSB_HW0_OP_EN, |
| ACCDET_AUXADC_RQST_CH5, |
| ACCDET_AUXADC_ACCDET_AUTO_SPL, |
| ACCDET_RG_ACCDET_CK_PDN, |
| ACCDET_RG_ACCDET_RST, |
| ACCDET_RG_INT_EN_ACCDET, |
| ACCDET_RG_INT_MASK_ACCDET, |
| ACCDET_RG_INT_STATUS_ACCDET, |
| ACCDET_AUDIO_DIG_ANA_ID, |
| ACCDET_RG_NCP_PDDIS_EN, |
| ACCDET_RG_AUDPREAMPLON, |
| ACCDET_RG_AUDPWDBMICBIAS0, |
| ACCDET_RG_AUDPWDBMICBIAS1, |
| ACCDET_RG_AUDACCDETMICBIAS0PULLLOW, |
| ACCDET_RG_EINT0CONFIGACCDET, |
| ACCDET_RG_EINT0HIRENB, |
| ACCDET_RG_EINT0NOHYS, |
| ACCDET_RG_ACCDET2AUXSWEN, |
| ACCDET_RG_EINT1CONFIGACCDET, |
| ACCDET_RG_MTEST_EN, |
| ACCDET_RG_MTEST_SEL, |
| ACCDET_RG_EINTCOMPVTH, |
| ACCDET_RG_ANALOGFDEN, |
| ACCDET_RG_EINT0CTURBO, |
| ACCDET_RG_EINT1CTURBO, |
| ACCDET_RG_EINT0EN, |
| ACCDET_RG_EINT1EN, |
| ACCDET_RG_EINT0CMPEN, |
| ACCDET_RG_ACCDETSPARE, |
| ACCDET_RG_CLKSQ_EN, |
| ACCDET_RG_HPLOUTPUTSTBENH_VAUDP32, |
| ACCDET_RG_HPROUTPUTSTBENH_VAUDP32, |
| ACCDET_AUDACCDETAUXADCSWCTRL_SW, |
| ACCDET_AUDACCDETAUXADCSWCTRL_SEL, |
| ACCDET_AUXADC_SEL, |
| ACCDET_EINT_M_DETECT_EN, |
| ACCDET_EINT0_INVERTER_SW_EN, |
| ACCDET_EINT1_INVERTER_SW_EN, |
| ACCDET_EINT0_M_SW_EN, |
| ACCDET_EINT1_M_SW_EN, |
| ACCDET_SEQ_INIT, |
| ACCDET_EINT0_SW_EN, |
| ACCDET_EINT1_SW_EN, |
| ACCDET_SW_EN, |
| ACCDET_CMP_PWM_EN, |
| ACCDET_PWM_WIDTH, |
| ACCDET_PWM_THRESH, |
| ACCDET_RISE_DELAY, |
| ACCDET_EINT_CMPMEN_PWM_THRESH, |
| ACCDET_DEBOUNCE0, |
| ACCDET_DEBOUNCE1, |
| ACCDET_DEBOUNCE2, |
| ACCDET_DEBOUNCE3, |
| ACCDET_CONNECT_AUXADC_TIME_DIG, |
| ACCDET_EINT_DEBOUNCE0, |
| ACCDET_EINT_DEBOUNCE1, |
| ACCDET_EINT_DEBOUNCE2, |
| ACCDET_EINT_DEBOUNCE3, |
| ACCDET_EINT_INVERTER_DEBOUNCE, |
| ACCDET_IRQ, |
| ACCDET_EINT_M_PLUG_IN_NUM, |
| ACCDET_DA_STABLE, |
| ACCDET_HWMODE_EN, |
| ACCDET_CMPEN_SEL, |
| ACCDET_EINT_CMPMOUT_SEL, |
| ACCDET_EINT_CMPMEN_SEL, |
| ACCDET_EINT_CTURBO_SEL, |
| ACCDET_EINT0_CTURBO_SW, |
| ACCDET_CMPEN_SW, |
| ACCDET_AD_AUDACCDETCMPOB, |
| ACCDET_MEM_IN, |
| ACCDET_AD_EINT0CMPMOUT, |
| ACCDET_EINT0_MEM_IN, |
| ACCDET_AD_EINT0INVOUT, |
| ACCDET_EN, |
| ACCDET_MON_FLAG_EN, |
| }; |
| |
| static const u32 mt6359_aud_regs[] = { |
| [TOP_TOP0_ANA_ID] = 0x0000, |
| [TOP_SWCID] = 0x000a, |
| [TOP_RG_RTC32K_CK_PDN] = 0x010c, |
| [TOP_INT_STATUS_AUD_TOP] = 0x019e, |
| [PLT_CPU_INT_STA] = 0x0452, |
| [LDO_RG_LDO_VUSB_HW0_OP_EN] = 0x1d0c, |
| [ACCDET_AUXADC_RQST_CH5] = 0x1108, |
| [ACCDET_AUXADC_ACCDET_AUTO_SPL] = 0x11ba, |
| [ACCDET_RG_ACCDET_CK_PDN] = 0x230c, |
| [ACCDET_RG_ACCDET_RST] = 0x2320, |
| [ACCDET_RG_INT_EN_ACCDET] = 0x2328, |
| [ACCDET_RG_INT_MASK_ACCDET] = 0x232e, |
| [ACCDET_RG_INT_STATUS_ACCDET] = 0x2334, |
| [ACCDET_AUDIO_DIG_ANA_ID] = 0x2380, |
| [ACCDET_RG_NCP_PDDIS_EN] = 0x24e2, |
| [ACCDET_RG_AUDPREAMPLON] = 0x2508, |
| [ACCDET_RG_AUDPWDBMICBIAS0] = 0x2526, |
| [ACCDET_RG_AUDPWDBMICBIAS1] = 0x2528, |
| [ACCDET_RG_AUDACCDETMICBIAS0PULLLOW] = 0x252c, |
| [ACCDET_RG_EINT0CONFIGACCDET] = 0x252c, |
| [ACCDET_RG_EINT0HIRENB] = 0x252c, |
| [ACCDET_RG_EINT0NOHYS] = 0x252c, |
| [ACCDET_RG_ACCDET2AUXSWEN] = 0x252c, |
| [ACCDET_RG_EINT1CONFIGACCDET] = 0x252e, |
| [ACCDET_RG_MTEST_EN] = 0x252e, |
| [ACCDET_RG_MTEST_SEL] = 0x252e, |
| [ACCDET_RG_EINTCOMPVTH] = 0x252e, |
| [ACCDET_RG_ANALOGFDEN] = 0x252e, |
| [ACCDET_RG_EINT0CTURBO] = 0x2530, |
| [ACCDET_RG_EINT1CTURBO] = 0x2530, |
| [ACCDET_RG_EINT0EN] = 0x2530, |
| [ACCDET_RG_EINT1EN] = 0x2530, |
| [ACCDET_RG_EINT0CMPEN] = 0x2530, |
| [ACCDET_RG_ACCDETSPARE] = 0x2532, |
| [ACCDET_RG_CLKSQ_EN] = 0x2536, |
| [ACCDET_RG_HPLOUTPUTSTBENH_VAUDP32] = 0x258c, |
| [ACCDET_RG_HPROUTPUTSTBENH_VAUDP32] = 0x258c, |
| [ACCDET_AUDACCDETAUXADCSWCTRL_SW] = 0x2688, |
| [ACCDET_AUDACCDETAUXADCSWCTRL_SEL] = 0x2688, |
| [ACCDET_AUXADC_SEL] = 0x2688, |
| [ACCDET_EINT_M_DETECT_EN] = 0x268a, |
| [ACCDET_EINT0_INVERTER_SW_EN] = 0x268a, |
| [ACCDET_EINT1_INVERTER_SW_EN] = 0x268a, |
| [ACCDET_EINT0_M_SW_EN] = 0x268a, |
| [ACCDET_EINT1_M_SW_EN] = 0x268a, |
| [ACCDET_SEQ_INIT] = 0x268a, |
| [ACCDET_EINT0_SW_EN] = 0x268a, |
| [ACCDET_EINT1_SW_EN] = 0x268a, |
| [ACCDET_SW_EN] = 0x268a, |
| [ACCDET_CMP_PWM_EN] = 0x268c, |
| [ACCDET_PWM_WIDTH] = 0x268e, |
| [ACCDET_PWM_THRESH] = 0x2690, |
| [ACCDET_RISE_DELAY] = 0x2692, |
| [ACCDET_EINT_CMPMEN_PWM_THRESH] = 0x2694, |
| [ACCDET_DEBOUNCE0] = 0x2698, |
| [ACCDET_DEBOUNCE1] = 0x269a, |
| [ACCDET_DEBOUNCE2] = 0x269c, |
| [ACCDET_DEBOUNCE3] = 0x269e, |
| [ACCDET_CONNECT_AUXADC_TIME_DIG] = 0x26a0, |
| [ACCDET_EINT_DEBOUNCE0] = 0x26a4, |
| [ACCDET_EINT_DEBOUNCE1] = 0x26a4, |
| [ACCDET_EINT_DEBOUNCE2] = 0x26a4, |
| [ACCDET_EINT_DEBOUNCE3] = 0x26a4, |
| [ACCDET_EINT_INVERTER_DEBOUNCE] = 0x26a6, |
| [ACCDET_IRQ] = 0x26ac, |
| [ACCDET_EINT_M_PLUG_IN_NUM] = 0x26ac, |
| [ACCDET_DA_STABLE] = 0x26ae, |
| [ACCDET_HWMODE_EN] = 0x26b0, |
| [ACCDET_CMPEN_SEL] = 0x26b4, |
| [ACCDET_EINT_CMPMOUT_SEL] = 0x26b4, |
| [ACCDET_EINT_CMPMEN_SEL] = 0x26b4, |
| [ACCDET_EINT_CTURBO_SEL] = 0x26b4, |
| [ACCDET_EINT0_CTURBO_SW] = 0x26b6, |
| [ACCDET_CMPEN_SW] = 0x26b6, |
| [ACCDET_AD_AUDACCDETCMPOB] = 0x26ba, |
| [ACCDET_MEM_IN] = 0x26ba, |
| [ACCDET_AD_EINT0CMPMOUT] = 0x26bc, |
| [ACCDET_EINT0_MEM_IN] = 0x26bc, |
| [ACCDET_AD_EINT0INVOUT] = 0x26c0, |
| [ACCDET_EN] = 0x26c4, |
| [ACCDET_MON_FLAG_EN] = 0x26d8, |
| }; |
| |
| struct mt63xx_accdet_data { |
| u32 base; |
| struct snd_soc_card card; |
| 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 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; |
| }; |
| static struct mt63xx_accdet_data *accdet; |
| |
| static struct head_dts_data accdet_dts; |
| struct pwm_deb_settings *cust_pwm_deb; |
| |
| struct accdet_priv { |
| const u32 *regs; |
| u32 caps; |
| struct snd_card *snd_card; |
| }; |
| |
| static struct accdet_priv mt6359_accdet[] = { |
| { |
| .regs = mt6359_aud_regs, |
| .caps = ACCDET_PMIC_EINT_IRQ | ACCDET_PMIC_EINT0 |
| | ACCDET_PMIC_INVERTER_EINT | ACCDET_THREE_KEY, |
| }, |
| }; |
| |
| const struct of_device_id accdet_of_match[] = { |
| { |
| .compatible = "mediatek,mt6359-accdet", |
| .data = &mt6359_accdet, |
| }, { |
| .compatible = "mediatek,mt8163-accdet", |
| }, { |
| .compatible = "mediatek,mt8173-accdet", |
| }, { |
| /* sentinel */ |
| }, |
| }; |
| |
| static struct snd_soc_jack_pin accdet_jack_pins[] = { |
| { |
| .pin = "Headset", |
| .mask = SND_JACK_HEADSET | |
| SND_JACK_LINEOUT | |
| SND_JACK_MECHANICAL, |
| }, |
| }; |
| |
| |
| /* 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 u32 button_press_debounce = 0x400; |
| |
| /* local function declaration */ |
| static void accdet_init_once(void); |
| static inline void accdet_init(void); |
| static void accdet_init_debounce(void); |
| static u32 adjust_eint_analog_setting(void); |
| static u32 adjust_eint_setting(u32 eintsts); |
| static void config_digital_init_by_mode(void); |
| static void config_eint_init_by_mode(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 send_status_event(u32 cable_type, u32 status); |
| /* global function declaration */ |
| inline u32 accdet_read(enum accdet_regs addr) |
| { |
| u32 val = 0; |
| |
| if (accdet->regmap) { |
| regmap_read(accdet->regmap, |
| accdet->base + accdet->data->regs[addr], &val); |
| } else |
| pr_notice("%s %d Error.\n", __func__, __LINE__); |
| |
| return val; |
| } |
| |
| inline u32 accdet_read_bits(enum accdet_regs addr, u32 shift, u32 mask) |
| { |
| u32 val = 0; |
| |
| val = accdet_read(addr); |
| return ((val>>shift) & mask); |
| } |
| |
| inline void accdet_write(enum accdet_regs addr, u32 wdata) |
| { |
| if (accdet->regmap) { |
| regmap_write(accdet->regmap, |
| accdet->base + accdet->data->regs[addr], wdata); |
| } else |
| pr_notice("%s %d Error.\n", __func__, __LINE__); |
| } |
| |
| inline void accdet_update_bits(enum accdet_regs addr, u32 shift, |
| u32 mask, u32 data) |
| { |
| regmap_update_bits(accdet->regmap, |
| accdet->base + accdet->data->regs[addr], |
| mask << shift, |
| data << shift); |
| } |
| |
| inline void accdet_update_bit(enum accdet_regs addr, unsigned int shift) |
| { |
| unsigned int mask = shift; |
| |
| regmap_update_bits(accdet->regmap, |
| accdet->base + accdet->data->regs[addr], |
| BIT(mask), |
| BIT(shift)); |
| } |
| |
| inline void accdet_clear_bits(enum accdet_regs addr, unsigned int shift, |
| unsigned int mask, unsigned int data) |
| { |
| regmap_update_bits(accdet->regmap, |
| accdet->base + accdet->data->regs[addr], |
| mask << shift, |
| ~(data << shift)); |
| } |
| |
| inline void accdet_clear_bit(enum accdet_regs addr, unsigned int shift) |
| { |
| unsigned int mask = shift; |
| |
| regmap_update_bits(accdet->regmap, |
| accdet->base + accdet->data->regs[addr], |
| BIT(mask), |
| ~(BIT(shift))); |
| } |
| |
| 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; |
| |
| /* accdet offset efuse: |
| * this efuse must divided by 2 |
| */ |
| ret = nvmem_device_read(accdet->accdet_efuse, 102*2, 2, &efuseval); |
| accdet->auxadc_offset = efuseval & 0xFF; |
| if (accdet->auxadc_offset > 128) |
| accdet->auxadc_offset -= 256; |
| accdet->auxadc_offset = (accdet->auxadc_offset >> 1); |
| 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, 103*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, 104*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; |
| |
| irq_bit = !(accdet_read(ACCDET_IRQ) & 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, PMIC_ACCDET_IRQ_CLR_SHIFT); |
| } |
| |
| static inline void clear_accdet_int_check(void) |
| { |
| u64 cur_time = accdet_get_current_time(); |
| |
| while ((accdet_read(ACCDET_IRQ) & ACCDET_IRQ_B0) && |
| (accdet_timeout_ns(cur_time, ACCDET_TIME_OUT))) |
| ; |
| /* clear accdet int, modify for fix interrupt trigger twice error */ |
| accdet_clear_bit(ACCDET_IRQ, PMIC_ACCDET_IRQ_CLR_SHIFT); |
| accdet_update_bit(ACCDET_RG_INT_STATUS_ACCDET, |
| PMIC_RG_INT_STATUS_ACCDET_SHIFT); |
| } |
| |
| static inline void clear_accdet_eint(u32 eintid) |
| { |
| if ((eintid & PMIC_EINT0) == PMIC_EINT0) { |
| accdet_update_bit(ACCDET_IRQ, |
| PMIC_ACCDET_EINT0_IRQ_CLR_SHIFT); |
| } |
| if ((eintid & PMIC_EINT1) == PMIC_EINT1) { |
| accdet_update_bit(ACCDET_IRQ, |
| PMIC_ACCDET_EINT1_IRQ_CLR_SHIFT); |
| } |
| |
| } |
| |
| static inline void clear_accdet_eint_check(u32 eintid) |
| { |
| u64 cur_time = accdet_get_current_time(); |
| |
| if ((eintid & PMIC_EINT0) == PMIC_EINT0) { |
| while ((accdet_read(ACCDET_IRQ) & ACCDET_EINT0_IRQ_B2) |
| && (accdet_timeout_ns(cur_time, ACCDET_TIME_OUT))) |
| ; |
| accdet_clear_bit(ACCDET_IRQ, |
| PMIC_ACCDET_EINT0_IRQ_CLR_SHIFT); |
| accdet_update_bit(ACCDET_RG_INT_STATUS_ACCDET, |
| PMIC_RG_INT_STATUS_ACCDET_EINT0_SHIFT); |
| } |
| if ((eintid & PMIC_EINT1) == PMIC_EINT1) { |
| while ((accdet_read(ACCDET_IRQ) & ACCDET_EINT1_IRQ_B3) |
| && (accdet_timeout_ns(cur_time, ACCDET_TIME_OUT))) |
| ; |
| accdet_clear_bit(ACCDET_IRQ, |
| PMIC_ACCDET_EINT1_IRQ_CLR_SHIFT); |
| accdet_update_bit(ACCDET_RG_INT_STATUS_ACCDET, |
| PMIC_RG_INT_STATUS_ACCDET_EINT1_SHIFT); |
| } |
| } |
| |
| static u32 adjust_eint_analog_setting(void) |
| { |
| if (accdet_dts.eint_detect_mode == 0x4) { |
| /* ESD switches off */ |
| accdet_clear_bit(ACCDET_RG_ACCDETSPARE, 8); |
| } |
| if (accdet_dts.eint_detect_mode == 0x4) { |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| /* enable RG_EINT0CONFIGACCDET */ |
| accdet_update_bit(ACCDET_RG_EINT0CONFIGACCDET, |
| PMIC_RG_EINT0CONFIGACCDET_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* enable RG_EINT1CONFIGACCDET */ |
| accdet_update_bit(ACCDET_RG_EINT1CONFIGACCDET, |
| PMIC_RG_EINT1CONFIGACCDET_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* enable RG_EINT0CONFIGACCDET */ |
| accdet_update_bit(ACCDET_RG_EINT0CONFIGACCDET, |
| PMIC_RG_EINT0CONFIGACCDET_SHIFT); |
| /* enable RG_EINT1CONFIGACCDET */ |
| accdet_update_bit(ACCDET_RG_EINT1CONFIGACCDET, |
| PMIC_RG_EINT1CONFIGACCDET_SHIFT); |
| } |
| } |
| return 0; |
| } |
| |
| static u32 adjust_eint_digital_setting(void) |
| { |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT0)) { |
| /* disable inverter */ |
| accdet_clear_bit(ACCDET_EINT0_INVERTER_SW_EN, |
| PMIC_ACCDET_EINT0_INVERTER_SW_EN_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT1)) { |
| /* disable inverter */ |
| accdet_clear_bit(ACCDET_EINT1_INVERTER_SW_EN, |
| PMIC_ACCDET_EINT1_INVERTER_SW_EN_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_BI_EINT)) { |
| /* disable inverter */ |
| accdet_clear_bit(ACCDET_EINT0_INVERTER_SW_EN, |
| PMIC_ACCDET_EINT0_INVERTER_SW_EN_SHIFT); |
| /* disable inverter */ |
| accdet_clear_bit(ACCDET_EINT1_INVERTER_SW_EN, |
| PMIC_ACCDET_EINT1_INVERTER_SW_EN_SHIFT); |
| } |
| |
| 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, |
| PMIC_ACCDET_EINT0_CEN_STABLE_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* set DA stable signal */ |
| accdet_clear_bit(ACCDET_DA_STABLE, |
| PMIC_ACCDET_EINT1_CEN_STABLE_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* set DA stable signal */ |
| accdet_clear_bit(ACCDET_DA_STABLE, |
| PMIC_ACCDET_EINT0_CEN_STABLE_SHIFT); |
| /* set DA stable signal */ |
| accdet_clear_bit(ACCDET_DA_STABLE, |
| PMIC_ACCDET_EINT1_CEN_STABLE_SHIFT); |
| } |
| } |
| 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); |
| } |
| |
| return 0; |
| } |
| |
| static void recover_eint_analog_setting(void) |
| { |
| if (accdet_dts.eint_detect_mode == 0x4) { |
| /* ESD switches on */ |
| accdet_update_bit(ACCDET_RG_ACCDETSPARE, 8); |
| |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| /* disable RG_EINT0CONFIGACCDET */ |
| accdet_clear_bit(ACCDET_RG_EINT0CONFIGACCDET, |
| PMIC_RG_EINT0CONFIGACCDET_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* disable RG_EINT1CONFIGACCDET */ |
| accdet_clear_bit(ACCDET_RG_EINT1CONFIGACCDET, |
| PMIC_RG_EINT1CONFIGACCDET_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* disable RG_EINT0CONFIGACCDET */ |
| accdet_clear_bit(ACCDET_RG_EINT0CONFIGACCDET, |
| PMIC_RG_EINT0CONFIGACCDET_SHIFT); |
| /* disable RG_EINT0CONFIGACCDET */ |
| accdet_clear_bit(ACCDET_RG_EINT1CONFIGACCDET, |
| PMIC_RG_EINT1CONFIGACCDET_SHIFT); |
| } |
| accdet_clear_bit(ACCDET_RG_EINT0HIRENB, |
| PMIC_RG_EINT0HIRENB_SHIFT); |
| } |
| |
| } |
| static void recover_eint_digital_setting(void) |
| { |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| accdet_clear_bit(ACCDET_EINT0_M_SW_EN, |
| PMIC_ACCDET_EINT0_M_SW_EN_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| accdet_clear_bit(ACCDET_EINT1_M_SW_EN, |
| PMIC_ACCDET_EINT1_M_SW_EN_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| accdet_clear_bit(ACCDET_EINT0_M_SW_EN, |
| PMIC_ACCDET_EINT0_M_SW_EN_SHIFT); |
| accdet_clear_bit(ACCDET_EINT1_M_SW_EN, |
| PMIC_ACCDET_EINT1_M_SW_EN_SHIFT); |
| } |
| 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, |
| PMIC_ACCDET_EINT0_CEN_STABLE_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* enable eint1cen */ |
| accdet_update_bit(ACCDET_DA_STABLE, |
| PMIC_ACCDET_EINT1_CEN_STABLE_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* enable eint0cen */ |
| accdet_update_bit(ACCDET_DA_STABLE, |
| PMIC_ACCDET_EINT0_CEN_STABLE_SHIFT); |
| /* enable eint1cen */ |
| accdet_update_bit(ACCDET_DA_STABLE, |
| PMIC_ACCDET_EINT1_CEN_STABLE_SHIFT); |
| } |
| } |
| |
| 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, |
| PMIC_ACCDET_EINT0_INVERTER_SW_EN_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| /* enable inverter */ |
| accdet_update_bit(ACCDET_EINT1_INVERTER_SW_EN, |
| PMIC_ACCDET_EINT1_INVERTER_SW_EN_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| /* enable inverter */ |
| accdet_update_bit(ACCDET_EINT0_INVERTER_SW_EN, |
| PMIC_ACCDET_EINT0_INVERTER_SW_EN_SHIFT); |
| /* enable inverter */ |
| accdet_update_bit(ACCDET_EINT1_INVERTER_SW_EN, |
| PMIC_ACCDET_EINT1_INVERTER_SW_EN_SHIFT); |
| } |
| } |
| } |
| |
| 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); |
| |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| if ((irq_status & ACCDET_EINT0_IRQ_B2) == ACCDET_EINT0_IRQ_B2) |
| eint_ID = PMIC_EINT0; |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| if ((irq_status & ACCDET_EINT1_IRQ_B3) == ACCDET_EINT1_IRQ_B3) |
| eint_ID = PMIC_EINT1; |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| if ((irq_status & ACCDET_EINT0_IRQ_B2) == ACCDET_EINT0_IRQ_B2) |
| eint_ID |= PMIC_EINT0; |
| if ((irq_status & ACCDET_EINT1_IRQ_B3) == ACCDET_EINT1_IRQ_B3) |
| eint_ID |= PMIC_EINT1; |
| } |
| return eint_ID; |
| } |
| |
| static inline void enable_accdet(u32 state_swctrl) |
| { |
| /* enable ACCDET unit */ |
| accdet_update_bit(ACCDET_SW_EN, PMIC_ACCDET_SW_EN_SHIFT); |
| } |
| |
| 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_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, eintID; |
| |
| /* check EINT0 status, if plug out, |
| * not need to disable accdet here |
| */ |
| eintID = accdet_read_bits(ACCDET_EINT0_MEM_IN, |
| PMIC_ACCDET_EINT0_MEM_IN_SHIFT, |
| PMIC_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) >> 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, |
| PMIC_ACCDET_SW_EN_SHIFT); |
| disable_accdet(); |
| } |
| } |
| |
| static void eint_work_callback(struct work_struct *work) |
| { |
| if (accdet->cur_eint_state == EINT_PLUG_IN) { |
| /* disable vusb LP */ |
| accdet_write(LDO_RG_LDO_VUSB_HW0_OP_EN, 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); |
| del_timer_sync(&micbias_timer); |
| |
| /* disable accdet_sw_en=0 |
| */ |
| accdet_clear_bit(ACCDET_SW_EN, |
| PMIC_ACCDET_SW_EN_SHIFT); |
| disable_accdet(); |
| headset_plug_out(); |
| } |
| |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT_IRQ)) |
| 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, debounce); |
| break; |
| case accdet_state001: |
| accdet_write(ACCDET_DEBOUNCE1, debounce); |
| break; |
| case accdet_state010: |
| accdet_write(ACCDET_DEBOUNCE2, debounce); |
| break; |
| case accdet_state011: |
| accdet_write(ACCDET_DEBOUNCE3, debounce); |
| break; |
| case accdet_auxadc: |
| /* set auxadc debounce:0x42(2ms) */ |
| accdet_write(ACCDET_CONNECT_AUXADC_TIME_DIG, debounce); |
| break; |
| case eint_state000: |
| accdet_update_bits(ACCDET_EINT_DEBOUNCE0, |
| PMIC_ACCDET_EINT_DEBOUNCE0_SHIFT, |
| PMIC_ACCDET_EINT_DEBOUNCE0_MASK, |
| debounce); |
| break; |
| case eint_state001: |
| accdet_update_bits(ACCDET_EINT_DEBOUNCE1, |
| PMIC_ACCDET_EINT_DEBOUNCE1_SHIFT, |
| PMIC_ACCDET_EINT_DEBOUNCE1_MASK, |
| debounce); |
| break; |
| case eint_state010: |
| accdet_update_bits(ACCDET_EINT_DEBOUNCE2, |
| PMIC_ACCDET_EINT_DEBOUNCE2_SHIFT, |
| PMIC_ACCDET_EINT_DEBOUNCE2_MASK, |
| debounce); |
| break; |
| case eint_state011: |
| accdet_update_bits(ACCDET_EINT_DEBOUNCE3, |
| PMIC_ACCDET_EINT_DEBOUNCE3_SHIFT, |
| PMIC_ACCDET_EINT_DEBOUNCE3_MASK, |
| debounce); |
| break; |
| case eint_inverter_state000: |
| accdet_write(ACCDET_EINT_INVERTER_DEBOUNCE, debounce); |
| break; |
| default: |
| pr_notice("Error: %s error state (%d)\n", __func__, state); |
| break; |
| } |
| } |
| |
| static inline void check_cable_type(void) |
| { |
| u32 cur_AB; |
| |
| cur_AB = accdet_read(ACCDET_MEM_IN) >> 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); |
| /* 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); |
| /* 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) { |
| /* enable vusb LP */ |
| accdet_write(LDO_RG_LDO_VUSB_HW0_OP_EN, 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; |
| |
| 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; |
| |
| 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; |
| |
| 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) { |
| 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; |
| } |
| |
| void accdet_irq_handle(void) |
| { |
| u32 eintID = 0; |
| u32 irq_status, acc_sts, eint_sts; |
| |
| eintID = get_triggered_eint(); |
| irq_status = accdet_read(ACCDET_IRQ); |
| acc_sts = accdet_read(ACCDET_MEM_IN); |
| eint_sts = accdet_read(ACCDET_EINT0_MEM_IN); |
| |
| if ((irq_status & ACCDET_IRQ_B0) && (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, |
| PMIC_ACCDET_EINT0_MEM_IN_SHIFT, |
| PMIC_ACCDET_EINT0_MEM_IN_MASK); |
| /* 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; |
| |
| 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; |
| struct device_node *node = NULL; |
| int pwm_deb[15]; |
| int three_key[4]; |
| |
| 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; |
| } |
| |
| 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; |
| } |
| accdet_dts.eint_detect_mode = 0x4; |
| |
| 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; |
| |
| 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)); |
| } |
| 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; |
| dis_micbias_done = false; |
| |
| return 0; |
| } |
| |
| static void config_digital_init_by_mode(void) |
| { |
| /* enable eint cmpmem pwm */ |
| accdet_write(ACCDET_EINT_CMPMEN_PWM_THRESH, |
| (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, |
| ACCDET_EINT0_STABLE_VAL); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| accdet_write(ACCDET_DA_STABLE, |
| ACCDET_EINT1_STABLE_VAL); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| accdet_write(ACCDET_DA_STABLE, |
| ACCDET_EINT0_STABLE_VAL); |
| accdet_write(ACCDET_DA_STABLE, |
| ACCDET_EINT1_STABLE_VAL); |
| } |
| /* after receive n+1 number, interrupt issued. now is 2 times */ |
| accdet_update_bit(ACCDET_EINT_M_PLUG_IN_NUM, |
| PMIC_ACCDET_EINT_M_PLUG_IN_NUM_SHIFT); |
| /* disable hwmode */ |
| accdet_write(ACCDET_HWMODE_EN, 0x100); |
| |
| accdet_clear_bit(ACCDET_EINT_M_DETECT_EN, |
| PMIC_ACCDET_EINT_M_DETECT_EN_SHIFT); |
| /* enable PWM */ |
| accdet_write(ACCDET_CMP_PWM_EN, 0x67); |
| /* enable inverter detection */ |
| if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT0)) { |
| accdet_update_bit(ACCDET_EINT0_INVERTER_SW_EN, |
| PMIC_ACCDET_EINT0_INVERTER_SW_EN_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_EINT1)) { |
| accdet_update_bit(ACCDET_EINT1_INVERTER_SW_EN, |
| PMIC_ACCDET_EINT1_INVERTER_SW_EN_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, |
| ACCDET_PMIC_BI_EINT)) { |
| accdet_update_bit(ACCDET_EINT0_INVERTER_SW_EN, |
| PMIC_ACCDET_EINT0_INVERTER_SW_EN_SHIFT); |
| accdet_update_bit(ACCDET_EINT1_INVERTER_SW_EN, |
| PMIC_ACCDET_EINT1_INVERTER_SW_EN_SHIFT); |
| } |
| } |
| |
| static void config_eint_init_by_mode(void) |
| { |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT0)) { |
| accdet_update_bit(ACCDET_RG_EINT0EN, |
| PMIC_RG_EINT0EN_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT1)) { |
| accdet_update_bit(ACCDET_RG_EINT1EN, |
| PMIC_RG_EINT1EN_SHIFT); |
| } else if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_BI_EINT)) { |
| accdet_update_bit(ACCDET_RG_EINT0EN, |
| PMIC_RG_EINT0EN_SHIFT); |
| accdet_update_bit(ACCDET_RG_EINT1EN, |
| PMIC_RG_EINT1EN_SHIFT); |
| } |
| /* ESD switches on */ |
| accdet_update_bit(ACCDET_RG_ACCDETSPARE, 8); |
| /* before playback, set NCP pull low before nagative voltage */ |
| accdet_update_bit(ACCDET_RG_NCP_PDDIS_EN, PMIC_RG_NCP_PDDIS_EN_SHIFT); |
| |
| if (accdet_dts.eint_detect_mode != 0x1) { |
| /* current detect set 0.25uA */ |
| accdet_update_bits(ACCDET_RG_ACCDETSPARE, |
| PMIC_RG_ACCDETSPARE_SHIFT, |
| 0x3, 0x3); |
| } |
| } |
| |
| static void accdet_init_once(void) |
| { |
| unsigned int reg = 0; |
| |
| /* reset the accdet unit */ |
| accdet_update_bit(ACCDET_RG_ACCDET_RST, PMIC_RG_ACCDET_RST_SHIFT); |
| accdet_clear_bit(ACCDET_RG_ACCDET_RST, PMIC_RG_ACCDET_RST_SHIFT); |
| |
| /* clear high micbias1 voltage setting */ |
| accdet_clear_bits(ACCDET_RG_AUDPWDBMICBIAS1, |
| PMIC_RG_AUDMICBIAS1HVEN_SHIFT, 0x3, 0x3); |
| /* clear micbias1 voltage */ |
| accdet_clear_bits(ACCDET_RG_AUDPWDBMICBIAS1, |
| PMIC_RG_AUDMICBIAS1VREF_SHIFT, 0x7, 0x7); |
| |
| /* init pwm frequency, duty & rise/falling delay */ |
| accdet_write(ACCDET_PWM_WIDTH, |
| REGISTER_VAL(cust_pwm_deb->pwm_width)); |
| accdet_write(ACCDET_PWM_THRESH, |
| REGISTER_VAL(cust_pwm_deb->pwm_thresh)); |
| accdet_write(ACCDET_RISE_DELAY, |
| (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(ACCDET_RG_AUDPWDBMICBIAS1); |
| if (accdet_dts.mic_vol <= 7) { |
| /* micbias1 <= 2.7V */ |
| accdet_write(ACCDET_RG_AUDPWDBMICBIAS1, |
| reg | (accdet_dts.mic_vol<<PMIC_RG_AUDMICBIAS1VREF_SHIFT) | |
| RG_AUD_MICBIAS1_LOWP_EN); |
| } else if (accdet_dts.mic_vol == 8) { |
| /* micbias1 = 2.8v */ |
| accdet_write(ACCDET_RG_AUDPWDBMICBIAS1, |
| reg | (3<<PMIC_RG_AUDMICBIAS1HVEN_SHIFT) | |
| RG_AUD_MICBIAS1_LOWP_EN); |
| } else if (accdet_dts.mic_vol == 9) { |
| /* micbias1 = 2.85v */ |
| accdet_write(ACCDET_RG_AUDPWDBMICBIAS1, |
| reg | (1<<PMIC_RG_AUDMICBIAS1HVEN_SHIFT) | |
| RG_AUD_MICBIAS1_LOWP_EN); |
| } |
| /* mic mode setting */ |
| reg = accdet_read(ACCDET_RG_AUDACCDETMICBIAS0PULLLOW); |
| if (accdet_dts.mic_mode == HEADSET_MODE_1) { |
| /* ACC mode*/ |
| accdet_write(ACCDET_RG_AUDACCDETMICBIAS0PULLLOW, |
| reg | RG_ACCDET_MODE_ANA11_MODE1); |
| /* enable analog fast discharge */ |
| accdet_update_bit(ACCDET_RG_ANALOGFDEN, |
| PMIC_RG_ANALOGFDEN_SHIFT); |
| accdet_update_bits(ACCDET_RG_ACCDETSPARE, 11, 0x3, 0x3); |
| } else if (accdet_dts.mic_mode == HEADSET_MODE_2) { |
| /* DCC mode Low cost mode without internal bias*/ |
| accdet_write(ACCDET_RG_AUDACCDETMICBIAS0PULLLOW, |
| reg | RG_ACCDET_MODE_ANA11_MODE2); |
| /* enable analog fast discharge */ |
| accdet_update_bits(ACCDET_RG_ANALOGFDEN, |
| PMIC_RG_ANALOGFDEN_SHIFT, 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(ACCDET_RG_AUDACCDETMICBIAS0PULLLOW, |
| reg | RG_ACCDET_MODE_ANA11_MODE6); |
| accdet_update_bit(ACCDET_RG_AUDPWDBMICBIAS1, |
| PMIC_RG_AUDMICBIAS1DCSW1PEN_SHIFT); |
| /* enable analog fast discharge */ |
| accdet_update_bits(ACCDET_RG_ANALOGFDEN, |
| PMIC_RG_ANALOGFDEN_SHIFT, 0x3, 0x3); |
| } |
| |
| if (HAS_CAP(accdet->data->caps, ACCDET_PMIC_EINT_IRQ)) { |
| config_eint_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(ACCDET_RG_AUDACCDETMICBIAS0PULLLOW, |
| 0x8F); |
| /* disconnect configaccdet */ |
| accdet_write(ACCDET_RG_EINT1CONFIGACCDET, |
| 0x0); |
| /* disable eint comparator */ |
| accdet_write(ACCDET_RG_EINT0CMPEN, 0x0); |
| /* enable PWM */ |
| accdet_write(ACCDET_CMP_PWM_EN, 0x7); |
| /* enable accdet sw mode */ |
| accdet_write(ACCDET_HWMODE_EN, 0x0); |
| /* set DA signal to stable */ |
| accdet_write(ACCDET_DA_STABLE, 0x1); |
| /* disable eint/inverter/sw_en */ |
| accdet_write(ACCDET_SW_EN, 0x0); |
| } |
| } |
| |
| 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, PMIC_ACCDET_SEQ_INIT_SHIFT); |
| usleep_range(2000, 3000); |
| accdet_clear_bit(ACCDET_SEQ_INIT, PMIC_ACCDET_SEQ_INIT_SHIFT); |
| 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); |
| accdet_set_debounce(eint_state001, |
| accdet_dts.pwm_deb.eint_debounce1); |
| accdet_set_debounce(eint_inverter_state000, |
| accdet_dts.pwm_deb.eint_inverter_debounce); |
| |
| } |
| |
| 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; |
| accdet->card.dev = &pdev->dev; |
| accdet->card.owner = THIS_MODULE; |
| ret = snd_soc_of_parse_card_name(&accdet->card, "accdet-name"); |
| if (ret) { |
| dev_dbg(&pdev->dev, "Error: Parse card name failed (%d)\n", |
| ret); |
| return ret; |
| } |
| |
| /* 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"); |
| accdet->timer_lock = wakeup_source_register(NULL, "accdet_timer_lock"); |
| mutex_init(&accdet->res_lock); |
| |
| platform_set_drvdata(pdev, accdet); |
| snd_soc_card_set_drvdata(&accdet->card, accdet); |
| ret = devm_snd_soc_register_card(&pdev->dev, &accdet->card); |
| if (ret) { |
| dev_dbg(&pdev->dev, "Error: Register card failed (%d)\n", |
| ret); |
| return ret; |
| } |
| accdet->data->snd_card = accdet->card.snd_card; |
| ret = snd_soc_card_jack_new(&accdet->card, |
| accdet_jack_pins[0].pin, |
| accdet_jack_pins[0].mask, |
| &accdet->jack, accdet_jack_pins, 1); |
| if (ret) { |
| dev_dbg(&pdev->dev, "Error: New card jack failed (%d)\n", |
| ret); |
| 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); |
| |
| /* Important. must to register */ |
| ret = snd_card_register(accdet->card.snd_card); |
| |
| 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; |
| |
| /* Create workqueue */ |
| 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 orkqueue 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); |
| } |
| |
| accdet_init(); |
| accdet_init_debounce(); |
| accdet_init_once(); |
| |
| return 0; |
| |
| err_create_workqueue: |
| destroy_workqueue(accdet->dis_micbias_workqueue); |
| err: |
| destroy_workqueue(accdet->accdet_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); |
| 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"); |