From d660a92d73f99c29c0bd6a12c5a74a255859b8af Mon Sep 17 00:00:00 2001 From: "Peter.Yang" Date: Mon, 2 Apr 2018 08:43:20 +0000 Subject: [PATCH] Add: headset detection for 6-Mics Circular Array Kit and 4-Mics Linear Array Kit --- ac101.c | 423 ++++++++++++++++++++++++++++++- ac101_regs.h | 2 + ac10x.h | 27 ++ seeed-8mic-voicecard-overlay.dts | 9 +- seeed-8mic-voicecard.dtbo | Bin 3374 -> 3484 bytes 5 files changed, 456 insertions(+), 5 deletions(-) diff --git a/ac101.c b/ac101.c index f4a6abd..9a79fc8 100644 --- a/ac101.c +++ b/ac101.c @@ -36,11 +36,13 @@ #include #include #include +#include #include "ac101_regs.h" #include "ac10x.h" /* * *** To sync channels *** + * * 1. disable clock in codec hw_params() * 2. clear fifo in bcm2835 hw_params() * 3. clear fifo in bcm2385 prepare() @@ -94,6 +96,385 @@ int ac101_update_bits(struct snd_soc_codec *codec, unsigned reg, return regmap_update_bits(ac10x->regmap101, reg, mask, value); } + + +#ifdef CONFIG_AC101_SWITCH_DETECT +/******************************************************************************/ +/********************************switch****************************************/ +/******************************************************************************/ +#define KEY_HEADSETHOOK 226 /* key define */ +#define HEADSET_FILTER_CNT (10) + +#define _AC101_CREATE_QUEUE 0 + +#if _AC101_CREATE_QUEUE +static struct workqueue_struct *queue_switch_detect; +static struct workqueue_struct *queue_codec_irq; +#endif + +/* + * switch_hw_config:config the 53 codec register + */ +static void switch_hw_config(struct snd_soc_codec *codec) +{ + int r; + + AC101_DBG("%s,line:%d\n",__func__,__LINE__); + + /*HMIC/MMIC BIAS voltage level select:2.5v*/ + ac101_update_bits(codec, OMIXER_BST1_CTRL, (0xf<state:%d\n", __func__, __LINE__, ac10x->state); + + input_report_switch(ac10x->inpdev, SW_HEADPHONE_INSERT, ac10x->state); + input_sync(ac10x->inpdev); + return; +} + +/* + * work_cb_clear_irq: clear audiocodec pending and Record the interrupt. + */ +static void work_cb_clear_irq(struct work_struct *work) +{ + int reg_val = 0; + struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, work_clear_irq); + struct snd_soc_codec *codec = ac10x->codec; + + ac10x->irq_cntr++; + + reg_val = ac101_read(codec, HMIC_STS); + if (BIT(HMIC_PULLOUT_PEND) & reg_val) { + ac10x->pullout_cntr++; + AC101_DBG("ac10x->pullout_cntr: %d\n",ac10x->pullout_cntr); + } + + reg_val |= HMIC_PEND_ALL; + ac101_write(codec, HMIC_STS, reg_val); + + reg_val = ac101_read(codec, HMIC_STS); + if ((reg_val & HMIC_PEND_ALL) != 0){ + reg_val |= HMIC_PEND_ALL; + ac101_write(codec, HMIC_STS, reg_val); + } + + if (cancel_work_sync(&ac10x->work_switch) != 0) { + ac10x->irq_cntr--; + } + + #if _AC101_CREATE_QUEUE + if (0 == queue_work(queue_switch_detect, &ac10x->work_switch)) { + #else + if (0 == schedule_work(&ac10x->work_switch)) { + #endif + ac10x->irq_cntr--; + AC101_DBG("[work_cb_clear_irq] add work struct failed!\n"); + } +} + +enum { + HBIAS_LEVEL_1 = 0x02, + HBIAS_LEVEL_2 = 0x0B, + HBIAS_LEVEL_3 = 0x13, + HBIAS_LEVEL_4 = 0x17, + HBIAS_LEVEL_5 = 0x19, +}; + +static int __ac101_get_hmic_data(struct snd_soc_codec *codec) { + static long counter; + int r; + int d; + + d = GET_HMIC_DATA(ac101_read(codec, HMIC_STS)); + + r = 0x1 << HMIC_DATA_PEND; + ac101_write(codec, HMIC_STS, r); + + AC101_DBG("%s,line:%d HMIC_DATA(%3ld): %02X\n", __func__, __LINE__, + counter++, d + ); + return d; +} + +/* + * work_cb_earphone_switch: judge the status of the headphone + */ +static void work_cb_earphone_switch(struct work_struct *work) +{ + struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, work_switch); + struct snd_soc_codec *codec = ac10x->codec; + + static int hook_flag1 = 0, hook_flag2 = 0; + static int KEY_VOLUME_FLAG = 0; + + unsigned filter_buf = 0; + int filt_index = 0; + int t = 0; + + ac10x->irq_cntr--; + + /* read HMIC_DATA */ + t = __ac101_get_hmic_data(codec); + + if ((t >= HBIAS_LEVEL_2) && (ac10x->mode == FOUR_HEADPHONE_PLUGIN)) { + t = __ac101_get_hmic_data(codec); + + if (t >= HBIAS_LEVEL_5){ + msleep(150); + t = __ac101_get_hmic_data(codec); + if (((t < HBIAS_LEVEL_2 && t >= HBIAS_LEVEL_1 - 1) || t >= HBIAS_LEVEL_5) + && (ac10x->pullout_cntr == 0)) { + input_report_key(ac10x->inpdev, KEY_HEADSETHOOK, 1); + input_sync(ac10x->inpdev); + + AC101_DBG("%s,line:%d KEY_HEADSETHOOK1\n", __func__, __LINE__); + + if (hook_flag1 != hook_flag2) + hook_flag1 = hook_flag2 = 0; + hook_flag1++; + } + if (ac10x->pullout_cntr) + ac10x->pullout_cntr--; + } else if (t >= HBIAS_LEVEL_4) { + msleep(80); + t = __ac101_get_hmic_data(codec); + if (t < HBIAS_LEVEL_5 && t >= HBIAS_LEVEL_4 && (ac10x->pullout_cntr == 0)) { + KEY_VOLUME_FLAG = 1; + input_report_key(ac10x->inpdev, KEY_VOLUMEUP, 1); + input_sync(ac10x->inpdev); + input_report_key(ac10x->inpdev, KEY_VOLUMEUP, 0); + input_sync(ac10x->inpdev); + + AC101_DBG("%s,line:%d HMIC_DATA: %d KEY_VOLUMEUP\n", __func__, __LINE__, t); + } + if (ac10x->pullout_cntr) + ac10x->pullout_cntr--; + } else if (t >= HBIAS_LEVEL_3){ + msleep(80); + t = __ac101_get_hmic_data(codec); + if (t < HBIAS_LEVEL_4 && t >= HBIAS_LEVEL_3 && (ac10x->pullout_cntr == 0)){ + KEY_VOLUME_FLAG = 1; + input_report_key(ac10x->inpdev, KEY_VOLUMEDOWN, 1); + input_sync(ac10x->inpdev); + input_report_key(ac10x->inpdev, KEY_VOLUMEDOWN, 0); + input_sync(ac10x->inpdev); + AC101_DBG("%s,line:%d KEY_VOLUMEDOWN\n", __func__, __LINE__); + } + if (ac10x->pullout_cntr) + ac10x->pullout_cntr--; + } + } else if ((t < HBIAS_LEVEL_2 && t >= HBIAS_LEVEL_1) && (ac10x->mode == FOUR_HEADPHONE_PLUGIN)) { + t = __ac101_get_hmic_data(codec); + if (t < HBIAS_LEVEL_2 && t >= HBIAS_LEVEL_1) { + if (KEY_VOLUME_FLAG) { + KEY_VOLUME_FLAG = 0; + } + if (hook_flag1 == (++hook_flag2)) { + hook_flag1 = hook_flag2 = 0; + input_report_key(ac10x->inpdev, KEY_HEADSETHOOK, 0); + input_sync(ac10x->inpdev); + + AC101_DBG("%s,line:%d KEY_HEADSETHOOK0\n", __func__, __LINE__); + } + } + } else { + while (ac10x->irq_cntr == 0) { + msleep(20); + + t = __ac101_get_hmic_data(codec); + + if (filt_index <= HEADSET_FILTER_CNT) { + if (filt_index++ == 0) { + filter_buf = t; + } else if (filter_buf != t) { + filt_index = 0; + } + continue; + } + + filt_index = 0; + if (filter_buf >= HBIAS_LEVEL_2) { + ac10x->mode = THREE_HEADPHONE_PLUGIN; + ac10x->state = 2; + } else if (filter_buf >= HBIAS_LEVEL_1 - 1) { + ac10x->mode = FOUR_HEADPHONE_PLUGIN; + ac10x->state = 1; + } else { + ac10x->mode = HEADPHONE_IDLE; + ac10x->state = 0; + } + switch_status_update(ac10x); + ac10x->pullout_cntr = 0; + break; + } + } +} + +/* + * audio_hmic_irq: the interrupt handlers + */ +static irqreturn_t audio_hmic_irq(int irq, void *para) +{ + struct ac10x_priv *ac10x = (struct ac10x_priv *)para; + if (ac10x == NULL) { + return -EINVAL; + } + + #if _AC101_CREATE_QUEUE + if (queue_codec_irq == NULL) { + AC101_DBG("------------queue_codec_irq is null !!----------"); + return IRQ_NONE; + } + if (0 == queue_work(queue_codec_irq, &ac10x->work_clear_irq)){ + + #else + if (0 == schedule_work(&ac10x->work_clear_irq)){ + #endif + + AC101_DBG("[audio_hmic_irq] work already in queue_codec_irq, adding failed!\n"); + } + return IRQ_HANDLED; +} + +static int ac101_switch_probe(struct ac10x_priv *ac10x) { + struct i2c_client *i2c = ac10x->i2c101; + int ret; + + ac10x->gpiod_irq = devm_gpiod_get_optional(&i2c->dev, "switch-irq", GPIOD_IN); + if (IS_ERR(ac10x->gpiod_irq)) { + ac10x->gpiod_irq = NULL; + dev_err(&i2c->dev, "failed get switch-irq in device tree\n"); + goto _err_irq; + } + + gpiod_direction_input(ac10x->gpiod_irq); + + ac10x->irq = gpiod_to_irq(ac10x->gpiod_irq); + if (IS_ERR_VALUE(ac10x->irq)) { + pr_info("[ac101] map gpio to irq failed, errno = %d\n", ac10x->irq); + ac10x->irq = 0; + goto _err_irq; + } + + /* request irq, set irq type to falling edge trigger */ + ret = devm_request_irq(ac10x->codec->dev, ac10x->irq, audio_hmic_irq, IRQF_TRIGGER_FALLING, "SWTICH_EINT", ac10x); + if (IS_ERR_VALUE(ret)) { + pr_info("[ac101] request virq %d failed, errno = %d\n", ac10x->irq, ret); + goto _err_irq; + } + + ac10x->mode = HEADPHONE_IDLE; + ac10x->state = -1; + + /*use for judge the state of switch*/ + INIT_WORK(&ac10x->work_switch, work_cb_earphone_switch); + INIT_WORK(&ac10x->work_clear_irq, work_cb_clear_irq); + + /********************create input device************************/ + ac10x->inpdev = devm_input_allocate_device(ac10x->codec->dev); + if (!ac10x->inpdev) { + AC101_DBG("input_allocate_device: not enough memory for input device\n"); + ret = -ENOMEM; + goto _err_input_allocate_device; + } + + ac10x->inpdev->name = "seed-voicecard-headset"; + ac10x->inpdev->phys = dev_name(ac10x->codec->dev); + ac10x->inpdev->id.bustype = BUS_I2C; + ac10x->inpdev->dev.parent = ac10x->codec->dev; + input_set_drvdata(ac10x->inpdev, ac10x->codec); + + ac10x->inpdev->evbit[0] = BIT_MASK(EV_KEY) | BIT(EV_SW); + + set_bit(KEY_HEADSETHOOK, ac10x->inpdev->keybit); + set_bit(KEY_VOLUMEUP, ac10x->inpdev->keybit); + set_bit(KEY_VOLUMEDOWN, ac10x->inpdev->keybit); + input_set_capability(ac10x->inpdev, EV_SW, SW_HEADPHONE_INSERT); + + ret = input_register_device(ac10x->inpdev); + if (ret) { + AC101_DBG("input_register_device: input_register_device failed\n"); + goto _err_input_register_device; + } + + /* the first headset state checking */ + switch_hw_config(ac10x->codec); + ac10x->irq_cntr = 1; + #if _AC101_CREATE_QUEUE + queue_codec_irq = create_singlethread_workqueue("codec_irq"); + queue_switch_detect = create_singlethread_workqueue("codec_switch"); + if (queue_switch_detect == NULL || queue_codec_irq == NULL) { + AC101_DBG("try to create workqueue for codec failed!\n"); + ret = -ENOMEM; + goto _err_switch_work_queue; + } + + queue_work(queue_switch_detect, &ac10x->work_switch); + #else + schedule_work(&ac10x->work_switch); + #endif + + return 0; + +#if _AC101_CREATE_QUEUE +_err_switch_work_queue: + + input_unregister_device(ac10x->inpdev); +#endif +_err_input_register_device: +_err_input_allocate_device: + + if (ac10x->irq) { + devm_free_irq(&i2c->dev, ac10x->irq, NULL); + ac10x->irq = 0; + } +_err_irq: + return ret; +} +/******************************************************************************/ +/********************************switch****************************************/ +/******************************************************************************/ +#endif + + + void drc_config(struct snd_soc_codec *codec) { int reg_val; @@ -1019,8 +1400,15 @@ int ac101_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level le break; case SND_SOC_BIAS_STANDBY: AC101_DBG("%s,line:%d, SND_SOC_BIAS_STANDBY\n", __func__, __LINE__); + #ifdef CONFIG_AC101_SWITCH_DETECT + switch_hw_config(codec); + #endif break; case SND_SOC_BIAS_OFF: + #ifdef CONFIG_AC101_SWITCH_DETECT + ac101_update_bits(codec, ADC_APC_CTRL, (0x1<irq) { + devm_free_irq(codec->dev, ac10x->irq, NULL); + ac10x->irq = 0; + } + #endif + + return 0; } @@ -1106,6 +1510,11 @@ int ac101_codec_resume(struct snd_soc_codec *codec) return ret; } + #ifdef CONFIG_AC101_SWITCH_DETECT + ac10x->mode = HEADPHONE_IDLE; + ac10x->state = -1; + #endif + ac101_set_bias_level(codec, SND_SOC_BIAS_STANDBY); schedule_work(&ac10x->codec_resume); return 0; @@ -1166,12 +1575,23 @@ static struct attribute_group audio_debug_attr_group = { /***************************************************************************/ /************************************************************/ +static bool ac101_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case PLL_CTRL2: + case HMIC_STS: + return true; + } + return false; +} + static const struct regmap_config ac101_regmap = { .reg_bits = 8, .val_bits = 16, .reg_stride = 1, .max_register = 0xB5, .cache_type = REGCACHE_FLAT, + .volatile_reg = ac101_volatile_reg, }; /* Sync reg_cache from the hardware */ @@ -1246,6 +1666,7 @@ int ac101_probe(struct i2c_client *i2c, const struct i2c_device_id *id) ac10x->gpiod_spk_amp_gate = NULL; dev_err(&i2c->dev, "failed get spk-amp-switch in device tree\n"); } + return 0; } diff --git a/ac101_regs.h b/ac101_regs.h index c640f77..8be570e 100644 --- a/ac101_regs.h +++ b/ac101_regs.h @@ -247,11 +247,13 @@ /*HMIC_STS*/ #define HMIC_DATA 8 +#define GET_HMIC_DATA(r) (((r) >> HMIC_DATA) & 0x1F) #define HMIC_PULLOUT_PEND 4 #define HMIC_PLUGIN_PEND 3 #define HMIC_KEYUP_PEND 2 #define HMKC_KEYDOWN_PEND 1 #define HMIC_DATA_PEND 0 +#define HMIC_PEND_ALL (0x1F) /*DAC_DIG_CTRL*/ #define ENDA 15 diff --git a/ac10x.h b/ac10x.h index c450a50..6af88c5 100644 --- a/ac10x.h +++ b/ac10x.h @@ -26,6 +26,10 @@ #define _MASTER_AC101 1 #define _MASTER_MULTI_CODEC _MASTER_AC101 +/* enable headset detecting & headset button pressing */ +#define CONFIG_AC101_SWITCH_DETECT + + #ifdef AC101_DEBG #define AC101_DBG(format,args...) printk("[AC101] "format,##args) #else @@ -33,6 +37,14 @@ #endif +#ifdef CONFIG_AC101_SWITCH_DETECT +enum headphone_mode_u { + HEADPHONE_IDLE, + FOUR_HEADPHONE_PLUGIN, + THREE_HEADPHONE_PLUGIN, +}; +#endif + struct ac10x_priv { struct i2c_client *i2c[4]; struct regmap* i2cmap[4]; @@ -60,9 +72,24 @@ struct ac10x_priv { struct work_struct codec_resume; struct gpio_desc* gpiod_spk_amp_gate; + + #ifdef CONFIG_AC101_SWITCH_DETECT + struct gpio_desc* gpiod_irq; + int irq; + volatile int irq_cntr; + volatile int pullout_cntr; + volatile int state; + + enum headphone_mode_u mode; + struct work_struct work_switch; + struct work_struct work_clear_irq; + + struct input_dev* inpdev; + #endif /* member for ac101 .end */ }; + /* AC101 DAI operations */ int ac101_audio_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai); void ac101_aif_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai); diff --git a/seeed-8mic-voicecard-overlay.dts b/seeed-8mic-voicecard-overlay.dts index eb6d7f9..d5bc576 100644 --- a/seeed-8mic-voicecard-overlay.dts +++ b/seeed-8mic-voicecard-overlay.dts @@ -26,9 +26,9 @@ fragment@2 { target = <&gpio>; __overlay__ { - spk_amp_switch_pins: speaker_amp_switch_pins { - brcm,pins = <17>; - brcm,function = <1>; /* out */ + spk_amp_and_irq_pins: speaker_amp_and_irq_pins { + brcm,pins = <17 22>; + brcm,function = <1 0>; /* out in */ }; gpclk0_pins: gpclk0_pins { brcm,pins = <4>; @@ -47,8 +47,9 @@ ac101: ac101@1a{ compatible = "x-power,ac101"; pinctrl-names = "default"; - pinctrl-0 = <&spk_amp_switch_pins>,<&gpclk0_pins>; + pinctrl-0 = <&spk_amp_and_irq_pins>,<&gpclk0_pins>; spk-amp-switch-gpios = <&gpio 17 0>; + switch-irq-gpios = <&gpio 22 0>; reg = <0x1a>; #sound-dai-cells = <0>; }; diff --git a/seeed-8mic-voicecard.dtbo b/seeed-8mic-voicecard.dtbo index aee93d94b9ebc50511566db58c7bbf988b37037a..169e28efcd833ce71dc63e9490c991fd119947d2 100644 GIT binary patch delta 656 zcmZ`$JuE{}6uzhJYhPb0QHh@@@l%o}+8`on+R`Tw>2LxO4S@HrD96?B8Xuy z0M>$l!&J(6JqvG8Y%Z5hW>%JyW+qqoNh&9z4$gs6)BxINOV9+W(hW^fs~_s}6F?N3 zK&&qCj>FEpb_X@c5oa*)2qm!d6~1yNzw?QI^3$$8dgY3%vyOZyl%A|&igA;+u$4|1 z%%rqgm5~m`Wji-5O(}B?^7p)GwQc(jkaJY!kD%MFJ$@UVL(Amf(<1`>p?T?=b$3rk z5nCDBzO4+cRqda7!aYuJeC&=p9K;oOkJgXx`FYgS#1W6b<)3%xf~ffJQM`+7>-LDek3>m#UX55)`Xnbp){6we%8Ff;i=ybqlT zq}zIU=Xt^#qAYKC?*sDx1okAGJYxg{GM~UcEpyQrz&sbmgYkELY1cPQ9%~eS@=tyN DSTJ)i delta 582 zcmbOuy-rHw0`I@K3=F(_3=9kw3=CWsfV2h>3j(nK5CZ{I98i45MvV$ao#OJ$lH`o| zg3P>PkPI^rvjA}-5Q8+p_}M@<4TGfnDbc=0$HY$1DK@+j{;dB08@T> z@*C!Ou)H-6`5KmP#>bO6Se1kregQRdAT%?oPqtxA&kar~E{F#?14$+d%7$9Qm<8oC zCKr?N7oW<}y( zn_R&b%=m2b88#~>#t)M@*tJ;=fJXeDY{9M~fh9CmfL8NQu3*<^l%AZ+sWABjJ3pf$ zkR>zu3%f9*CYYtdAJm|)Sn%WxoXU(5ldo`I76pY22*6wo3K@pU4s7D0Apd{>Oa|m1hRJi-v_TOG^oIoZ S)y@C7ofz35ZlCPV8wvnw7-9VY