1604 lines
49 KiB
C
Executable file
1604 lines
49 KiB
C
Executable file
/*
|
|
* sound/soc/codec/s5m3700x-jack.c
|
|
*
|
|
* ALSA SoC Audio Layer - Samsung Codec Driver
|
|
*
|
|
* Copyright (C) 2020 Samsung Electronics
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/io.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/regmap.h>
|
|
#include <sound/soc.h>
|
|
#include <linux/input.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <uapi/linux/input-event-codes.h>
|
|
|
|
#include "s5m3700x.h"
|
|
#include "s5m3700x-jack.h"
|
|
|
|
static BLOCKING_NOTIFIER_HEAD(s5m3700x_notifier);
|
|
struct notifier_block s5m3700x_notifier_block;
|
|
|
|
struct s5m3700x_notifier_struct {
|
|
struct s5m3700x_priv *s5m3700x;
|
|
};
|
|
static struct s5m3700x_notifier_struct s5m3700x_notifier_t;
|
|
|
|
int s5m3700x_register_notifier(struct notifier_block *n,
|
|
struct s5m3700x_priv *s5m3700x)
|
|
{
|
|
int ret;
|
|
|
|
s5m3700x_notifier_t.s5m3700x = s5m3700x;
|
|
ret = blocking_notifier_chain_register(&s5m3700x_notifier, n);
|
|
if (ret < 0)
|
|
pr_err("[IRQ] %s(%d)\n", __func__, __LINE__);
|
|
return ret;
|
|
}
|
|
|
|
static void jack_wake_lock(struct wakeup_source *ws)
|
|
{
|
|
__pm_stay_awake(ws);
|
|
}
|
|
|
|
static void jack_wake_unlock(struct wakeup_source *ws)
|
|
{
|
|
__pm_relax(ws);
|
|
}
|
|
|
|
static int jack_set_wake_lock(struct s5m3700x_jack *jackdet)
|
|
{
|
|
struct wakeup_source *ws = NULL;
|
|
ws = wakeup_source_register(NULL, "s5m3700x-jack");
|
|
|
|
if (ws == NULL)
|
|
goto err;
|
|
|
|
jackdet->jack_wakeup = ws;
|
|
|
|
return 0;
|
|
err:
|
|
return -1;
|
|
}
|
|
/*
|
|
* mute_mic() - Mute mic if it is active
|
|
*
|
|
* @s5m3700x: codec information struct
|
|
* @on: mic mute is true, mic unmute is false
|
|
*
|
|
* Desc: It can mute adc depending on the parameter
|
|
*/
|
|
static void mute_mic(struct s5m3700x_priv *s5m3700x, bool on)
|
|
{
|
|
dev_info(s5m3700x->dev, "%s called, %s\n", __func__, on ? "Mute" : "Unmute");
|
|
|
|
if (on)
|
|
s5m3700x_adc_digital_mute(s5m3700x, ADC_MUTE_ALL, true);
|
|
else
|
|
s5m3700x_adc_digital_mute(s5m3700x, ADC_MUTE_ALL, false);
|
|
}
|
|
|
|
static int read_adc(struct s5m3700x_jack *jackdet, int type, int pin, int repeat)
|
|
{
|
|
struct s5m3700x_priv *s5m3700x = jackdet->p_s5m3700x;
|
|
u8 v1, v2;
|
|
unsigned int value = 0, i;
|
|
|
|
mutex_lock(&jackdet->adc_lock);
|
|
|
|
switch (pin) {
|
|
case JACK_LDET_ADC:
|
|
regcache_cache_switch(s5m3700x, false);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E4_DCTR_FSM5,
|
|
GPMUX_POLE_MASK, POLE_HPL_DET << GPMUX_POLE_SHIFT);
|
|
regcache_cache_switch(s5m3700x, true);
|
|
break;
|
|
case JACK_MDET_ADC:
|
|
regcache_cache_switch(s5m3700x, false);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E4_DCTR_FSM5,
|
|
GPMUX_POLE_MASK, POLE_MIC_DET << GPMUX_POLE_SHIFT);
|
|
regcache_cache_switch(s5m3700x, true);
|
|
break;
|
|
case JACK_IMP_ADC:
|
|
break;
|
|
case JACK_WTP_LDET_ADC:
|
|
regcache_cache_switch(s5m3700x, false);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E4_DCTR_FSM5,
|
|
GPMUX_WTP_MASK, POLE_HPL_DET << GPMUX_WTP_SHIFT);
|
|
regcache_cache_switch(s5m3700x, true);
|
|
break;
|
|
}
|
|
|
|
switch (type) {
|
|
case ADC_POLE:
|
|
regcache_cache_switch(s5m3700x, false);
|
|
for (i = 0; i < repeat; i++) {
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E5_DCTR_FSM6,
|
|
AP_REREAD_POLE_MASK, AP_REREAD_POLE_MASK);
|
|
s5m3700x_usleep(100);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E5_DCTR_FSM6,
|
|
AP_REREAD_POLE_MASK, 0);
|
|
jackdet->earjack_re_read = 1;
|
|
}
|
|
regcache_cache_switch(s5m3700x, true);
|
|
|
|
msleep(100);
|
|
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_F4_STATUS5, &v1);
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_F7_STATUS8, &v2);
|
|
|
|
v1 = v1 & 0x0F;
|
|
value = (v1 << 8) + v2;
|
|
break;
|
|
case ADC_BTN:
|
|
regcache_cache_switch(s5m3700x, false);
|
|
for (i = 0; i < repeat; i++) {
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E5_DCTR_FSM6,
|
|
AP_REREAD_BTN_MASK, AP_REREAD_BTN_MASK);
|
|
s5m3700x_usleep(100);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E5_DCTR_FSM6,
|
|
AP_REREAD_BTN_MASK, 0);
|
|
}
|
|
regcache_cache_switch(s5m3700x, true);
|
|
msleep(10);
|
|
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_F5_STATUS6, &v1);
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_F8_STATUS9, &v2);
|
|
|
|
v1 = v1 & 0xF0;
|
|
value = (v1 << 4) + v2;
|
|
break;
|
|
case ADC_IMP:
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_F5_STATUS6, &v1);
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_F9_STATUS10, &v2);
|
|
|
|
v1 = v1 & 0x0F;
|
|
value = (v1 << 8) + v2;
|
|
break;
|
|
case ADC_WTP:
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_F2_STATUS3, &v1);
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_F3_STATUS4, &v2);
|
|
|
|
v1 = v1 & 0xF0;
|
|
value = (v1 << 8) + v2;
|
|
break;
|
|
}
|
|
|
|
mutex_unlock(&jackdet->adc_lock);
|
|
dev_info(s5m3700x->dev, "%s called. ADC Pin: %d, Value: %d\n",
|
|
__func__, pin, value);
|
|
return value;
|
|
}
|
|
|
|
static void s5m3700x_read_imp_otp(struct s5m3700x_jack *jackdet)
|
|
{
|
|
struct s5m3700x_priv *s5m3700x = jackdet->p_s5m3700x;
|
|
bool msb;
|
|
u8 jack_otp[4];
|
|
|
|
/* Jack OTP Setting */
|
|
s5m3700x_i2c_read_reg(S5M3700X_OTP_ADDR, S5M3700X_2D2_JACK_OTP02, &jack_otp[0]);
|
|
s5m3700x_i2c_read_reg(S5M3700X_OTP_ADDR, S5M3700X_2D3_JACK_OTP03, &jack_otp[1]);
|
|
s5m3700x_i2c_read_reg(S5M3700X_OTP_ADDR, S5M3700X_2D4_JACK_OTP04, &jack_otp[2]);
|
|
s5m3700x_i2c_read_reg(S5M3700X_OTP_ADDR, S5M3700X_2D5_JACK_OTP05, &jack_otp[3]);
|
|
|
|
jackdet->imp_p[0] = jack_otp[1];
|
|
jackdet->imp_p[1] = ((jack_otp[0] & 0x70) << 4) + jack_otp[2];
|
|
jackdet->imp_p[2] = ((jack_otp[0] & 0x0C) << 6) + jack_otp[3];
|
|
msb = jackdet->imp_p[2] & 0x200;
|
|
|
|
if (msb)
|
|
jackdet->imp_p[2] = -(0x400 - jackdet->imp_p[2]);
|
|
|
|
dev_info(s5m3700x->dev, "%s called. p1: %d, p2: %d, p3: %d\n",
|
|
__func__, jackdet->imp_p[0],
|
|
jackdet->imp_p[1], jackdet->imp_p[2]);
|
|
}
|
|
|
|
static int calculate_imp(struct s5m3700x_jack *jackdet, unsigned int d_out)
|
|
{
|
|
long long p1 = jackdet->imp_p[0];
|
|
long long p2 = jackdet->imp_p[1];
|
|
long long p3 = jackdet->imp_p[2];
|
|
long long c1 = 99, c2 = 79, c3 = 670;
|
|
long long temp1, temp2, temp3, result;
|
|
|
|
if ((p1 == 0) || (p2 == 0) || (p3 == 0))
|
|
return 32;
|
|
|
|
temp1 = p1 * ((c3 * p1) + (long long)1024) *
|
|
((c2 * (long long)d_out) - (c2 * p3) - (c1 * p2) - (c2 * p2));
|
|
temp2 = p2 * ((c1 * p1) + (c2 * p1) + (c3 * p1) + (long long)1024);
|
|
temp3 = (d_out - p3) * ((c3 * p1) + (c2 * p1) + (long long)1024);
|
|
result = temp1 / ((long long)128 * (temp2 - temp3));
|
|
|
|
dev_info(jackdet->p_s5m3700x->dev,
|
|
"%s called. tp1: %lld, tp2: %lld, tp3: %lld\n",
|
|
__func__, temp1, temp2, temp3);
|
|
|
|
return result;
|
|
}
|
|
|
|
static int s5m3700x_get_imp(struct s5m3700x_jack *jackdet)
|
|
{
|
|
unsigned int imp_adc;
|
|
int cal;
|
|
|
|
imp_adc = read_adc(jackdet, ADC_IMP, JACK_IMP_ADC, 0);
|
|
cal = calculate_imp(jackdet, imp_adc);
|
|
|
|
dev_info(jackdet->p_s5m3700x->dev,
|
|
"%s called. impedance: %d\n", __func__, cal);
|
|
|
|
return cal;
|
|
}
|
|
|
|
static char *s5m3700x_return_status_name(unsigned int status)
|
|
{
|
|
switch (status) {
|
|
case JACK_OUT:
|
|
return "JACK_OUT";
|
|
case JACK_POLE_DEC:
|
|
return "JACK_POLE_DEC";
|
|
case JACK_3POLE:
|
|
return "JACK_3POLE";
|
|
case JACK_4POLE:
|
|
return "JACK_4POLE";
|
|
case JACK_AUX:
|
|
return "JACK_AUX";
|
|
case JACK_OMTP:
|
|
return "JACK_OMTP";
|
|
case JACK_CMP:
|
|
return "JACK_CMP";
|
|
case JACK_IMP:
|
|
return "JACK_IMP";
|
|
case JACK_WTP_DEC:
|
|
return "JACK_WTP_DEC";
|
|
case JACK_WTP_JIO:
|
|
return "JACK_WTP_JIO";
|
|
case JACK_WTP_CJI:
|
|
return "JACK_WTP_CJI";
|
|
case JACK_WTP_ETC:
|
|
return "JACK_WTP_ETC";
|
|
case JACK_WTP_POLL:
|
|
return "JACK_WTP_POLL";
|
|
case JACK_WTP_JO:
|
|
return "JACK_WTP_JO";
|
|
case JACK_IN:
|
|
return "JACK_IN";
|
|
default:
|
|
return "No status";
|
|
}
|
|
}
|
|
|
|
static void s5m3700x_print_status_change(struct device *dev, unsigned int prv_jack, unsigned int cur_jack)
|
|
{
|
|
dev_info(dev, "Priv: %s -> Cur: %s\n"
|
|
, s5m3700x_return_status_name(prv_jack), s5m3700x_return_status_name(cur_jack));
|
|
}
|
|
|
|
static bool s5m3700x_jackstate_register(struct s5m3700x_jack *jackdet)
|
|
{
|
|
struct s5m3700x_priv *s5m3700x = jackdet->p_s5m3700x;
|
|
struct earjack_state *jackstate = &jackdet->jack_state;
|
|
struct device *dev = jackdet->p_s5m3700x->dev;
|
|
unsigned int cur_jack, prv_jack, mic4_on, imp_value;
|
|
|
|
cur_jack = jackstate->cur_jack_state;
|
|
prv_jack = jackstate->prv_jack_state;
|
|
|
|
regcache_cache_switch(s5m3700x, false);
|
|
|
|
switch (cur_jack) {
|
|
case JACK_OUT:
|
|
s5m3700x_read(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_1E_CHOP1, &mic4_on);
|
|
mic4_on &= MIC4_ON_MASK;
|
|
|
|
if (mic4_on)
|
|
mute_mic(s5m3700x, true);
|
|
|
|
if (prv_jack & (JACK_IN | JACK_WTP_ST | JACK_POLE_DEC | JACK_IMP | JACK_CMP)) {
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
jackdet->earjack_re_read = 0;
|
|
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_3A_AD_VOL, 0x00);
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E0_DCTR_FSM1, 0x0A);
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E3_DCTR_FSM4, 0x00);
|
|
|
|
/* ODSEL5 */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_B4_ODSEL5,
|
|
EN_DAC1_OUTTIE_MASK|EN_DAC2_OUTTIE_MASK, EN_DAC1_OUTTIE_MASK|EN_DAC2_OUTTIE_MASK);
|
|
} else {
|
|
goto err;
|
|
}
|
|
break;
|
|
case JACK_CMP:
|
|
if (prv_jack & (JACK_OUT | JACK_WTP_CJI | JACK_WTP_ETC))
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
else
|
|
goto err;
|
|
break;
|
|
case JACK_WTP_DEC:
|
|
if (prv_jack & (JACK_OUT | JACK_CMP | JACK_WTP_JIO))
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
else
|
|
goto err;
|
|
break;
|
|
case JACK_WTP_CJI:
|
|
if (prv_jack & JACK_WTP_DEC) {
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
/* Completely Jack In */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E0_DCTR_FSM1,
|
|
AP_CJI_MASK, AP_CJI_MASK);
|
|
s5m3700x_usleep(100);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E0_DCTR_FSM1,
|
|
AP_CJI_MASK, 0x0);
|
|
} else {
|
|
goto err;
|
|
}
|
|
break;
|
|
case JACK_WTP_ETC:
|
|
if (prv_jack & JACK_WTP_DEC) {
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
/* Slightly Jack In */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E0_DCTR_FSM1,
|
|
AP_ETC_MASK, AP_ETC_MASK);
|
|
s5m3700x_usleep(100);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E0_DCTR_FSM1,
|
|
AP_ETC_MASK, 0x0);
|
|
} else {
|
|
goto err;
|
|
}
|
|
break;
|
|
case JACK_WTP_POLL:
|
|
if (prv_jack & JACK_WTP_DEC) {
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
/* Water In */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E0_DCTR_FSM1,
|
|
AP_POLLING_MASK, AP_POLLING_MASK);
|
|
s5m3700x_usleep(100);
|
|
} else {
|
|
goto err;
|
|
}
|
|
break;
|
|
case JACK_WTP_JO:
|
|
if (prv_jack & JACK_WTP_DEC) {
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
/* Jack Out*/
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E0_DCTR_FSM1,
|
|
AP_JO_MASK, AP_JO_MASK);
|
|
s5m3700x_usleep(100);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E0_DCTR_FSM1,
|
|
AP_JO_MASK, 0x0);
|
|
} else {
|
|
goto err;
|
|
}
|
|
break;
|
|
case JACK_WTP_JIO:
|
|
if (prv_jack & JACK_WTP_POLL) {
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
/* Polling off */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E0_DCTR_FSM1,
|
|
AP_POLLING_MASK, 0x00);
|
|
s5m3700x_usleep(100);
|
|
/* GPADC Re-read */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E5_DCTR_FSM6,
|
|
AP_REREAD_WTP_MASK, AP_REREAD_WTP_MASK);
|
|
s5m3700x_usleep(100);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E5_DCTR_FSM6,
|
|
AP_REREAD_WTP_MASK, 0x00);
|
|
} else {
|
|
goto err;
|
|
}
|
|
break;
|
|
case JACK_IMP:
|
|
if (prv_jack & JACK_CMP) {
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
|
|
imp_value = s5m3700x_get_imp(jackdet);
|
|
|
|
if (imp_value < 10)
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_EE_ACTR_ETC1,
|
|
IMP_VAL_MASK, 2 << IMP_VAL_SHIFT);
|
|
else if (imp_value >= 10 && imp_value < 32)
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_EE_ACTR_ETC1,
|
|
IMP_VAL_MASK, 1 << IMP_VAL_SHIFT);
|
|
else
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_EE_ACTR_ETC1,
|
|
IMP_VAL_MASK, 0 << IMP_VAL_SHIFT);
|
|
} else {
|
|
goto err;
|
|
}
|
|
break;
|
|
case JACK_POLE_DEC:
|
|
if (prv_jack & (JACK_OUT | JACK_IMP))
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
else
|
|
goto err;
|
|
break;
|
|
case JACK_4POLE:
|
|
if (prv_jack & JACK_POLE_DEC) {
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E3_DCTR_FSM4,
|
|
POLE_VALUE_MASK, POLE_VALUE_4POLE << POLE_VALUE_SHIFT);
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_3A_AD_VOL, 0x07);
|
|
/* ODSEL5 */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_B4_ODSEL5,
|
|
EN_DAC1_OUTTIE_MASK|EN_DAC2_OUTTIE_MASK, 0x00);
|
|
} else {
|
|
goto err;
|
|
}
|
|
break;
|
|
case JACK_3POLE:
|
|
if (prv_jack & JACK_POLE_DEC) {
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E3_DCTR_FSM4,
|
|
POLE_VALUE_MASK, POLE_VALUE_3POLE << POLE_VALUE_SHIFT);
|
|
/* ODSEL5 */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_B4_ODSEL5,
|
|
EN_DAC1_OUTTIE_MASK|EN_DAC2_OUTTIE_MASK, 0x00);
|
|
} else {
|
|
goto err;
|
|
}
|
|
break;
|
|
case JACK_AUX:
|
|
if (prv_jack & (JACK_POLE_DEC | JACK_WTP_ETC)) {
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E3_DCTR_FSM4,
|
|
POLE_VALUE_MASK, POLE_VALUE_AUX << POLE_VALUE_SHIFT);
|
|
/* ODSEL5 */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_B4_ODSEL5,
|
|
EN_DAC1_OUTTIE_MASK|EN_DAC2_OUTTIE_MASK, 0x00);
|
|
} else {
|
|
goto err;
|
|
}
|
|
break;
|
|
case JACK_OMTP:
|
|
if (prv_jack & JACK_POLE_DEC) {
|
|
s5m3700x_print_status_change(dev, prv_jack, cur_jack);
|
|
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E3_DCTR_FSM4,
|
|
POLE_VALUE_MASK, POLE_VALUE_OMTP << POLE_VALUE_SHIFT);
|
|
/* ODSEL5 */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_B4_ODSEL5,
|
|
EN_DAC1_OUTTIE_MASK|EN_DAC2_OUTTIE_MASK, 0x00);
|
|
} else {
|
|
goto err;
|
|
}
|
|
break;
|
|
}
|
|
|
|
regcache_cache_switch(s5m3700x, true);
|
|
return true;
|
|
|
|
err:
|
|
regcache_cache_switch(s5m3700x, true);
|
|
dev_err(dev, "%s Jack state machine error! prv: 0x%02x, cur: 0x%02x\n",
|
|
__func__, jackstate->prv_jack_state, jackstate->cur_jack_state);
|
|
return false;
|
|
}
|
|
|
|
static void s5m3700x_jackstate_set(struct s5m3700x_jack *jackdet,
|
|
unsigned int change_state)
|
|
{
|
|
struct earjack_state *jackstate = &jackdet->jack_state;
|
|
struct device *dev = jackdet->p_s5m3700x->dev;
|
|
bool ret;
|
|
|
|
/* Save privious jack state */
|
|
jackstate->prv_jack_state = jackstate->cur_jack_state;
|
|
|
|
/* Update current jack state */
|
|
jackstate->cur_jack_state = change_state;
|
|
|
|
if (jackstate->prv_jack_state != jackstate->cur_jack_state) {
|
|
dev_info(dev, "%s called, Prv: 0x%02x, Cur: 0x%02x\n", __func__,
|
|
jackstate->prv_jack_state, jackstate->cur_jack_state);
|
|
|
|
/* Set jack register */
|
|
ret = s5m3700x_jackstate_register(jackdet);
|
|
dev_info(dev, "Jack register write %s.\n",
|
|
ret ? "complete" : "incomplete");
|
|
} else {
|
|
dev_info(dev, "Prv_jack_state and Cur_jack_state are same.\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* process_button_ev() - Process the button events
|
|
*
|
|
* @jackdet: jack information struct
|
|
* @on: button press is true, button release is false.
|
|
*
|
|
* Desc: It processes button press or release events. It gives information
|
|
* about the button state to framework layer.
|
|
*/
|
|
static void process_button_ev(struct s5m3700x_jack *jackdet, bool on)
|
|
{
|
|
struct earjack_state *jackstate = &jackdet->jack_state;
|
|
struct s5m3700x_priv *s5m3700x = jackdet->p_s5m3700x;
|
|
|
|
dev_info(s5m3700x->dev, "%s: key %d is %s, adc: %d\n",
|
|
__func__, jackstate->button_code,
|
|
on ? "pressed" : "released", jackstate->btn_adc);
|
|
|
|
input_report_key(jackdet->input, jackstate->button_code, on);
|
|
input_sync(jackdet->input);
|
|
mute_mic(s5m3700x, on);
|
|
}
|
|
|
|
/*
|
|
* before_button_error_chk() - Check button state error
|
|
*
|
|
* @jackdet: jack information struct
|
|
*
|
|
* Desc: Error check condition for button irq before current button state check.
|
|
* Return: There is not problem, return true.
|
|
*/
|
|
static bool before_button_error_chk(struct s5m3700x_jack *jackdet)
|
|
{
|
|
struct earjack_state *jackstate = &jackdet->jack_state;
|
|
struct device *dev = jackdet->p_s5m3700x->dev;
|
|
|
|
/* Terminate workqueue Cond1: jack is not detected */
|
|
if (jackstate->cur_jack_state & JACK_OUT) {
|
|
dev_err(dev, "Skip button events because jack is not detected.\n");
|
|
if (jackstate->cur_btn_state == BUTTON_PRESS) {
|
|
jackstate->prv_btn_state = BUTTON_RELEASE;
|
|
process_button_ev(jackdet, BUTTON_RELEASE);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Terminate workqueue Cond2: 3 pole earjack */
|
|
if (!(jackstate->cur_jack_state & JACK_4POLE)) {
|
|
dev_err(dev, "Skip button events for 3 pole earjack.\n");
|
|
return false;
|
|
}
|
|
|
|
/* Terminate workqueue Cond3: button adc error */
|
|
if (jackstate->btn_adc == -EINVAL) {
|
|
dev_err(dev, "Button ADC Error! BTN ADC: %d\n", jackstate->btn_adc);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* after_button_error_chk() - Check button state error
|
|
*
|
|
* @jackdet: jack information struct
|
|
*
|
|
* Desc: Error check condition for button irq after current button state check.
|
|
* Return: There is not problem, return true.
|
|
*/
|
|
static bool after_button_error_chk(struct s5m3700x_jack *jackdet)
|
|
{
|
|
struct earjack_state *jackstate = &jackdet->jack_state;
|
|
struct device *dev = jackdet->p_s5m3700x->dev;
|
|
|
|
/* Terminate workqueue Cond4: 3 pole earjack with button press */
|
|
if ((jackstate->cur_btn_state == BUTTON_PRESS) &&
|
|
!(jackstate->cur_jack_state & JACK_4POLE)) {
|
|
dev_err(dev, "Skip button press event for 3 pole earjack.\n");
|
|
jackstate->prv_btn_state = BUTTON_RELEASE;
|
|
process_button_ev(jackdet, BUTTON_RELEASE);
|
|
return false;
|
|
}
|
|
|
|
/* Terminate workqueue Cond5: button state not changed with button press */
|
|
if ((jackstate->cur_btn_state == BUTTON_PRESS) &&
|
|
(jackstate->prv_btn_state == jackstate->cur_btn_state)) {
|
|
dev_err(dev, "Skip button press event, button status are same.\n");
|
|
jackstate->prv_btn_state = BUTTON_RELEASE;
|
|
process_button_ev(jackdet, BUTTON_RELEASE);
|
|
return false;
|
|
}
|
|
|
|
/* Terminate workqueue Cond6: button state not changed */
|
|
if (jackstate->prv_btn_state == jackstate->cur_btn_state) {
|
|
dev_err(dev, "Button status are same.\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void reset_mic4_boost(struct s5m3700x_jack *jackdet)
|
|
{
|
|
struct s5m3700x_priv *s5m3700x = jackdet->p_s5m3700x;
|
|
|
|
regcache_cache_switch(s5m3700x, false);
|
|
|
|
/* Ear-mic BST Reset */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_AF_BST_CTR,
|
|
EN_ARESETB_BST4_MASK, EN_ARESETB_BST4_MASK);
|
|
s5m3700x_usleep(100);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_AF_BST_CTR,
|
|
EN_ARESETB_BST4_MASK, 0);
|
|
|
|
regcache_cache_switch(s5m3700x, true);
|
|
}
|
|
|
|
/*
|
|
* s5m3700x_buttons_work() - Process button interrupt
|
|
*
|
|
* @work: delayed workqueue
|
|
*
|
|
* Desc: When button interrupt is occurred, generated buttons_work process.
|
|
* Judge whether button press or release using button adc. And notify to
|
|
* framework layer.
|
|
*/
|
|
static void s5m3700x_buttons_work(struct work_struct *work)
|
|
{
|
|
struct s5m3700x_jack *jackdet =
|
|
container_of(work, struct s5m3700x_jack, buttons_work.work);
|
|
struct earjack_state *jackstate = &jackdet->jack_state;
|
|
struct device *dev = jackdet->p_s5m3700x->dev;
|
|
struct jack_buttons_zone *btn_zones = jackdet->jack_buttons_zones;
|
|
int num_buttons_zones = ARRAY_SIZE(jackdet->jack_buttons_zones);
|
|
int i;
|
|
|
|
/* Read adc value */
|
|
jackstate->btn_adc = read_adc(jackdet, ADC_BTN, JACK_MDET_ADC, 0);
|
|
|
|
/* Check error status */
|
|
if (!before_button_error_chk(jackdet)) {
|
|
jack_wake_unlock(jackdet->jack_wakeup);
|
|
return;
|
|
}
|
|
|
|
/* Check button press/release */
|
|
dev_info(dev, "Button %s! adc: %d, btn_release_value: %d\n",
|
|
jackstate->cur_btn_state == BUTTON_RELEASE ? "Released" : "Pressed",
|
|
jackstate->btn_adc, jackdet->btn_release_value);
|
|
|
|
/* Check error status */
|
|
if (!after_button_error_chk(jackdet)) {
|
|
jack_wake_unlock(jackdet->jack_wakeup);
|
|
return;
|
|
}
|
|
|
|
/* Save button information */
|
|
jackstate->prv_btn_state = jackstate->cur_btn_state;
|
|
|
|
if (jackstate->cur_btn_state == BUTTON_PRESS) {
|
|
/* Determine which button pressed */
|
|
for (i = 0; i < num_buttons_zones; i++) {
|
|
if (jackstate->btn_adc <= btn_zones[i].adc_high) {
|
|
jackstate->button_code = btn_zones[i].code;
|
|
dev_info(dev,
|
|
"%s: adc: %d, btn code: %d, low: %d, high: %d\n",
|
|
__func__, jackstate->btn_adc, jackstate->button_code,
|
|
btn_zones[i].adc_low, btn_zones[i].adc_high);
|
|
process_button_ev(jackdet, BUTTON_PRESS);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
/* Ear-mic BST4 Reset */
|
|
reset_mic4_boost(jackdet);
|
|
|
|
/* Button released */
|
|
process_button_ev(jackdet, BUTTON_RELEASE);
|
|
}
|
|
|
|
/* Terminate workqueue Cond7: Button press and release event duplicated. */
|
|
if (jackdet->btn_state == BUTTON_PRESS_RELEASE) {
|
|
jackdet->btn_state = BUTTON_RELEASE;
|
|
|
|
dev_err(dev, "Button press and release event duplicated.\n");
|
|
|
|
jackstate->prv_btn_state = BUTTON_RELEASE;
|
|
|
|
msleep(50);
|
|
|
|
/* Ear-mic BST4 Reset */
|
|
reset_mic4_boost(jackdet);
|
|
|
|
process_button_ev(jackdet, BUTTON_RELEASE);
|
|
}
|
|
|
|
jack_wake_unlock(jackdet->jack_wakeup);
|
|
}
|
|
|
|
static void s5m3700x_ldet_chk_work(struct work_struct *work)
|
|
{
|
|
struct s5m3700x_jack *jackdet =
|
|
container_of(work, struct s5m3700x_jack, ldet_chk_work.work);
|
|
struct earjack_state *jackstate = &jackdet->jack_state;
|
|
struct device *dev = jackdet->p_s5m3700x->dev;
|
|
unsigned int decision_state = JACK_WTP_JO;
|
|
|
|
dev_info(dev, "%s called, cur_jack_state: 0x%02x.\n",
|
|
__func__, jackstate->cur_jack_state);
|
|
|
|
/* Pole value decision */
|
|
if (jackstate->cur_jack_state & JACK_WTP_DEC) {
|
|
/* Read ldet adc value for Water Protection */
|
|
// need to change JACK_LDET_ADC to WTP ADC
|
|
jackstate->ldet_adc = read_adc(jackdet, ADC_WTP, JACK_WTP_LDET_ADC, 1);
|
|
|
|
if (jackstate->ldet_adc < jackdet->adc_thd_fake_jack) {
|
|
decision_state = JACK_WTP_CJI;
|
|
} else if ((jackstate->ldet_adc >= jackdet->adc_thd_fake_jack)
|
|
&& (jackstate->ldet_adc < jackdet->adc_thd_water_in)) {
|
|
decision_state = JACK_WTP_ETC;
|
|
} else if ((jackstate->ldet_adc >= jackdet->adc_thd_water_in)
|
|
&& (jackstate->ldet_adc < jackdet->adc_thd_water_out)) {
|
|
decision_state = JACK_WTP_POLL;
|
|
} else {
|
|
decision_state = JACK_WTP_JO;
|
|
}
|
|
s5m3700x_jackstate_set(jackdet, decision_state);
|
|
} else {
|
|
dev_info(dev, "%s called, Jack state is not JACK_WTP_DEC\n", __func__);
|
|
}
|
|
|
|
jack_wake_unlock(jackdet->jack_wakeup);
|
|
}
|
|
static char *s5m3700x_print_jack_type_state(int cur_jack_state)
|
|
{
|
|
if (cur_jack_state & JACK_4POLE)
|
|
return "4POLE";
|
|
else if (cur_jack_state & JACK_AUX)
|
|
return "AUX";
|
|
else
|
|
return "3POLE";
|
|
}
|
|
static void s5m3700x_jack_det_work(struct work_struct *work)
|
|
{
|
|
struct s5m3700x_jack *jackdet =
|
|
container_of(work, struct s5m3700x_jack, jack_det_work.work);
|
|
struct earjack_state *jackstate = &jackdet->jack_state;
|
|
struct device *dev = jackdet->p_s5m3700x->dev;
|
|
int jack_state = JACK_4POLE;
|
|
|
|
mutex_lock(&jackdet->mdet_lock);
|
|
dev_info(dev, "%s called, cur_jack_state: 0x%02x.\n",
|
|
__func__, jackstate->cur_jack_state);
|
|
|
|
/* Pole value decision */
|
|
if (jackstate->cur_jack_state & JACK_POLE_DEC) {
|
|
/* Read mdet adc value for detect mic */
|
|
jackstate->mdet_adc = read_adc(jackdet, ADC_POLE, JACK_MDET_ADC, 0);
|
|
|
|
if (jackstate->mdet_adc < jackdet->mic_adc_range[0])
|
|
jack_state = JACK_3POLE;
|
|
else if ((jackstate->mdet_adc >= jackdet->mic_adc_range[0]) &&
|
|
(jackstate->mdet_adc < jackdet->mic_adc_range[1]))
|
|
jack_state = JACK_4POLE;
|
|
else
|
|
jack_state = JACK_AUX;
|
|
|
|
/* Read ldet adc value for OMTP Jack */
|
|
if ((jackdet->omtp_range != S5M3700X_OMTP_ADC_DEFAULT)
|
|
&& (jack_state == JACK_4POLE)) {
|
|
jackstate->ldet_adc = read_adc(jackdet, ADC_POLE, JACK_LDET_ADC, 1);
|
|
if (jackstate->ldet_adc > jackdet->omtp_range)
|
|
jack_state = JACK_OMTP;
|
|
} else {
|
|
jackstate->ldet_adc = -EINVAL;
|
|
}
|
|
|
|
/* Report jack type */
|
|
s5m3700x_jackstate_set(jackdet, jack_state);
|
|
if (jack_state == JACK_4POLE)
|
|
input_report_switch(jackdet->input, SW_MICROPHONE_INSERT, 1);
|
|
else
|
|
input_report_switch(jackdet->input, SW_HEADPHONE_INSERT, 1);
|
|
input_sync(jackdet->input);
|
|
|
|
dev_info(dev, "%s ldet: %d, mdet: %d, Jack: %s, Pole: %s\n", __func__,
|
|
jackstate->ldet_adc, jackstate->mdet_adc,
|
|
(jackstate->cur_jack_state & JACK_OMTP) ? "OMTP" : "CTIA",
|
|
s5m3700x_print_jack_type_state(jackstate->cur_jack_state));
|
|
} else if (jackstate->cur_jack_state & JACK_WTP_ETC) {
|
|
/*
|
|
* Raise Aux state by WTP Process
|
|
*/
|
|
jack_state = JACK_AUX;
|
|
|
|
/* Report jack type */
|
|
s5m3700x_jackstate_set(jackdet, jack_state);
|
|
input_report_switch(jackdet->input, SW_HEADPHONE_INSERT, 1);
|
|
input_sync(jackdet->input);
|
|
|
|
dev_info(dev, "%s WTP decision Jack: CTIA, Pole: AUX\n", __func__);
|
|
} else if (jackstate->cur_jack_state & JACK_IN) {
|
|
/*
|
|
* Already Jack-in, Re-Check the Pole
|
|
*/
|
|
dev_dbg(dev, "%s called, Re-check the Pole\n", __func__);
|
|
input_sync(jackdet->input);
|
|
} else {
|
|
dev_info(dev, "%s called, Jack state is not JACK_POLE_DEC\n", __func__);
|
|
/* Send the jack out event to the audio framework */
|
|
input_report_switch(jackdet->input, SW_HEADPHONE_INSERT, 0);
|
|
input_report_switch(jackdet->input, SW_MICROPHONE_INSERT, 0);
|
|
input_sync(jackdet->input);
|
|
}
|
|
|
|
dev_info(dev, "%s called, Jack %s, Mic %s\n", __func__,
|
|
(jackstate->cur_jack_state & JACK_IN) ? "inserted" : "removed",
|
|
(jackstate->cur_jack_state & JACK_4POLE) ? "inserted" : "removed");
|
|
|
|
mutex_unlock(&jackdet->mdet_lock);
|
|
jack_wake_unlock(jackdet->jack_wakeup);
|
|
}
|
|
|
|
/*
|
|
* s5m3700x_configure_mic_bias() - Configure mic bias voltage
|
|
*
|
|
* @jackdet: jack information struct
|
|
*
|
|
* Desc: Configure the mic1 and mic2 bias voltages with default value
|
|
* or the value received from the device tree.
|
|
* Also configure the internal LDO voltage.
|
|
*/
|
|
static void s5m3700x_configure_mic_bias(struct s5m3700x_jack *jackdet)
|
|
{
|
|
struct s5m3700x_priv *s5m3700x = jackdet->p_s5m3700x;
|
|
|
|
/* Configure Mic1 Bias Voltage */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_C8_ACTR_MCB4,
|
|
CTRV_MCB1_MASK, jackdet->mic_bias1_voltage << CTRV_MCB1_SHIFT);
|
|
|
|
/* Configure Mic2 Bias Voltage */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_C9_ACTR_MCB5,
|
|
CTRV_MCB2_MASK, jackdet->mic_bias2_voltage << CTRV_MCB2_SHIFT);
|
|
|
|
/* Configure Mic3 Bias Voltage */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_C9_ACTR_MCB5,
|
|
CTRV_MCB3_MASK, jackdet->mic_bias3_voltage << CTRV_MCB3_SHIFT);
|
|
}
|
|
|
|
/*
|
|
* s5m3700x_jack_parse_dt() - Parsing device tree options about jack
|
|
*
|
|
* @s5m3700x: codec information struct
|
|
*
|
|
* Desc: Initialize jack_det struct variable as parsing device tree options.
|
|
* Customer can tune this options. If not set by customer,
|
|
* it use default value below defined.
|
|
*/
|
|
|
|
static void s5m3700x_jack_parse_dt(struct s5m3700x_priv *s5m3700x)
|
|
{
|
|
struct s5m3700x_jack *jackdet = s5m3700x->p_jackdet;
|
|
struct device *dev = s5m3700x->dev;
|
|
struct of_phandle_args args;
|
|
unsigned int tc, delay, mic_range;
|
|
unsigned int bias_v_conf, btn_rel_val;
|
|
int thd_adc;
|
|
int ret, i;
|
|
|
|
jackdet->irqb_gpio = of_get_named_gpio(dev->of_node, "s5m3700x-codec-int", 0);
|
|
if (jackdet->irqb_gpio < 0)
|
|
dev_err(dev, "%s cannot find irqb gpio in the dt\n", __func__);
|
|
else
|
|
dev_info(dev, "%s: irqb gpio = %d\n",
|
|
__func__, jackdet->irqb_gpio);
|
|
|
|
/*
|
|
* Setting ADC measurement value in test mode (TC, Delay).
|
|
*/
|
|
ret = of_property_read_u32(dev->of_node, "t-pole-cnt", &tc);
|
|
if (!ret)
|
|
jackdet->t_pole_cnt = tc;
|
|
else
|
|
jackdet->t_pole_cnt = 5;
|
|
|
|
ret = of_property_read_u32(dev->of_node, "t-btn-cnt", &tc);
|
|
if (!ret)
|
|
jackdet->t_btn_cnt = tc;
|
|
else
|
|
jackdet->t_btn_cnt = 4;
|
|
|
|
ret = of_property_read_u32(dev->of_node, "t-pole-delay", &delay);
|
|
if (!ret)
|
|
jackdet->t_pole_delay = delay;
|
|
else
|
|
jackdet->t_pole_delay = 10;
|
|
|
|
ret = of_property_read_u32(dev->of_node, "t-btn-delay", &delay);
|
|
if (!ret)
|
|
jackdet->t_btn_delay = delay;
|
|
else
|
|
jackdet->t_btn_delay = 4;
|
|
|
|
dev_info(dev, "TM ADC cnt: %d %d, delay: %d %d\n",
|
|
jackdet->t_pole_cnt, jackdet->t_btn_cnt,
|
|
jackdet->t_pole_delay, jackdet->t_btn_delay);
|
|
|
|
/*
|
|
* Setting ADC detecting range.
|
|
*/
|
|
/* Mic det adc range */
|
|
ret = of_parse_phandle_with_args(dev->of_node, "mic-adc-range", "#list-imp-cells", 0, &args);
|
|
if (!ret) {
|
|
for (i = 0; i < 2; i++)
|
|
jackdet->mic_adc_range[i] = args.args[i];
|
|
} else {
|
|
jackdet->mic_adc_range[0] = S5M3700X_MIC_ADC_RANGE_0;
|
|
jackdet->mic_adc_range[1] = S5M3700X_MIC_ADC_RANGE_1;
|
|
}
|
|
|
|
/* Mdet check delay */
|
|
ret = of_property_read_u32(dev->of_node, "mdet-chk-delay", &delay);
|
|
if (!ret)
|
|
jackdet->mdet_chk_delay = delay;
|
|
else
|
|
jackdet->mdet_chk_delay = S5M3700X_MDET_CHK_DEFAULT;
|
|
|
|
/* Ldet check delay */
|
|
ret = of_property_read_u32(dev->of_node, "ldet-chk-delay", &delay);
|
|
if (!ret)
|
|
jackdet->ldet_chk_delay = delay;
|
|
else
|
|
jackdet->ldet_chk_delay = S5M3700X_LDET_CHK_DEFAULT;
|
|
|
|
/* OMTP det adc range */
|
|
ret = of_property_read_u32(dev->of_node, "omtp-adc-range", &mic_range);
|
|
if (!ret)
|
|
jackdet->omtp_range = mic_range;
|
|
else
|
|
jackdet->omtp_range = S5M3700X_OMTP_ADC_DEFAULT;
|
|
|
|
dev_info(dev, "mic: %d %d, omtp: %d, mdet chk: %d\n",
|
|
jackdet->mic_adc_range[0], jackdet->mic_adc_range[1], jackdet->omtp_range,
|
|
jackdet->mdet_chk_delay);
|
|
|
|
/* Jack Imp adc range */
|
|
ret = of_parse_phandle_with_args(dev->of_node, "jack-imp-range", "#list-imp-cells", 0, &args);
|
|
if (!ret) {
|
|
for (i = 0; i < 5; i++)
|
|
jackdet->jack_imp_range[i] = args.args[i];
|
|
} else {
|
|
jackdet->jack_imp_range[0] = S5M3700X_JACK_IMP_RANGE_0;
|
|
jackdet->jack_imp_range[1] = S5M3700X_JACK_IMP_RANGE_1;
|
|
jackdet->jack_imp_range[2] = S5M3700X_JACK_IMP_RANGE_2;
|
|
jackdet->jack_imp_range[3] = S5M3700X_JACK_IMP_RANGE_3;
|
|
jackdet->jack_imp_range[4] = S5M3700X_JACK_IMP_RANGE_4;
|
|
}
|
|
|
|
dev_info(dev, "jack imp range: %d %d %d %d %d.\n",
|
|
jackdet->jack_imp_range[0],
|
|
jackdet->jack_imp_range[1],
|
|
jackdet->jack_imp_range[2],
|
|
jackdet->jack_imp_range[3],
|
|
jackdet->jack_imp_range[4]);
|
|
|
|
/*
|
|
* Set mic bias 1/2/3 voltages
|
|
*/
|
|
/* Mic Bias 1 */
|
|
ret = of_property_read_u32(dev->of_node, "mic-bias1-voltage", &bias_v_conf);
|
|
if (!ret &&
|
|
((bias_v_conf >= MICBIAS_VO_1_5V) &&
|
|
(bias_v_conf <= MICBIAS_VO_2_8V))) {
|
|
jackdet->mic_bias1_voltage = bias_v_conf;
|
|
} else {
|
|
jackdet->mic_bias1_voltage = MICBIAS_VO_2_8V;
|
|
dev_warn(dev, "Property 'mic-bias1-voltage' %s",
|
|
ret ? "not found, default set 2.8V" : "used invalid value");
|
|
}
|
|
|
|
/* Mic Bias 2 */
|
|
ret = of_property_read_u32(dev->of_node, "mic-bias2-voltage", &bias_v_conf);
|
|
if (!ret &&
|
|
((bias_v_conf >= MICBIAS_VO_1_5V) &&
|
|
(bias_v_conf <= MICBIAS_VO_2_8V))) {
|
|
jackdet->mic_bias2_voltage = bias_v_conf;
|
|
} else {
|
|
jackdet->mic_bias2_voltage = MICBIAS_VO_2_8V;
|
|
dev_warn(dev, "Property 'mic-bias2-voltage' %s",
|
|
ret ? "not found, default set 2.8V" : "used invalid value");
|
|
}
|
|
|
|
/* Mic Bias 3 */
|
|
ret = of_property_read_u32(dev->of_node, "mic-bias3-voltage", &bias_v_conf);
|
|
if (!ret &&
|
|
((bias_v_conf >= MICBIAS_VO_1_5V) &&
|
|
(bias_v_conf <= MICBIAS_VO_2_8V))) {
|
|
jackdet->mic_bias3_voltage = bias_v_conf;
|
|
} else {
|
|
jackdet->mic_bias3_voltage = MICBIAS_VO_2_8V;
|
|
dev_warn(dev, "Property 'mic-bias3-voltage' %s",
|
|
ret ? "not found, default set 2.8V" : "used invalid value");
|
|
}
|
|
|
|
dev_info(dev, "Bias voltage values: bias1=%d, bias2=%d, bias3=%d\n",
|
|
jackdet->mic_bias1_voltage,
|
|
jackdet->mic_bias2_voltage,
|
|
jackdet->mic_bias3_voltage);
|
|
|
|
/*
|
|
* Setting delay time for adc check
|
|
*/
|
|
/* Mic bias on delay time */
|
|
ret = of_property_read_u32(dev->of_node, "mic-det-delay", &delay);
|
|
if (!ret)
|
|
jackdet->mdet_delay = delay;
|
|
else
|
|
jackdet->mdet_delay = S5M3700X_MDET_DELAY;
|
|
|
|
/* Button adc check delay time */
|
|
ret = of_property_read_u32(dev->of_node, "btn-adc-delay", &delay);
|
|
if (!ret)
|
|
jackdet->btn_adc_delay = delay;
|
|
else
|
|
jackdet->btn_adc_delay = S5M3700X_BTN_ADC_DELAY;
|
|
|
|
dev_info(dev, "ADC check delay, mic: %d, btn: %d\n",
|
|
jackdet->mdet_delay, jackdet->btn_adc_delay);
|
|
|
|
/*
|
|
* Set button adc tuning values
|
|
*/
|
|
/* Button release adc value */
|
|
ret = of_property_read_u32(dev->of_node, "btn-release-value", &btn_rel_val);
|
|
if (!ret)
|
|
jackdet->btn_release_value = btn_rel_val;
|
|
else
|
|
jackdet->btn_release_value = S5M3700X_BTN_REL_DEFAULT;
|
|
|
|
/* Button press adc value, a maximum of 4 buttons are supported */
|
|
for (i = 0; i < 4; i++) {
|
|
if (of_parse_phandle_with_args(dev->of_node,
|
|
"but-zones-list", "#list-but-cells", i, &args))
|
|
break;
|
|
jackdet->jack_buttons_zones[i].code = args.args[0];
|
|
jackdet->jack_buttons_zones[i].adc_low = args.args[1];
|
|
jackdet->jack_buttons_zones[i].adc_high = args.args[2];
|
|
}
|
|
|
|
for (i = 0; i < 4; i++)
|
|
dev_info(dev, "Button Press: code(%d), low(%d), high(%d)\n",
|
|
jackdet->jack_buttons_zones[i].code,
|
|
jackdet->jack_buttons_zones[i].adc_low,
|
|
jackdet->jack_buttons_zones[i].adc_high);
|
|
dev_info(dev, "Button Release: %d\n", jackdet->btn_release_value);
|
|
|
|
/* Button debounce value */
|
|
ret = of_property_read_u32(dev->of_node, "btn-dbnc-value", &btn_rel_val);
|
|
if (!ret)
|
|
jackdet->btn_dbnc_value = btn_rel_val;
|
|
else
|
|
jackdet->btn_dbnc_value = S5M3700X_BTN_DBNC_DEFAULT;
|
|
|
|
/* Set ldet adc threshold value */
|
|
ret = of_property_read_u32(dev->of_node, "adc-thd-fake-jack", &thd_adc);
|
|
if (!ret)
|
|
jackdet->adc_thd_fake_jack = thd_adc;
|
|
else
|
|
jackdet->adc_thd_fake_jack = S5M3700X_ADC_THD_FAKE_JACK;
|
|
|
|
ret = of_property_read_u32(dev->of_node, "adc-thd-water-in", &thd_adc);
|
|
if (!ret)
|
|
jackdet->adc_thd_water_in = thd_adc;
|
|
else
|
|
jackdet->adc_thd_water_in = S5M3700X_ADC_THD_WATER_IN;
|
|
|
|
ret = of_property_read_u32(dev->of_node, "adc-thd-water-out", &thd_adc);
|
|
if (!ret)
|
|
jackdet->adc_thd_water_out = thd_adc;
|
|
else
|
|
jackdet->adc_thd_water_out = S5M3700X_ADC_THD_WATER_OUT;
|
|
|
|
dev_info(dev, "LDET adc threshold value: %d %d %d\n",
|
|
jackdet->adc_thd_fake_jack,
|
|
jackdet->adc_thd_water_in,
|
|
jackdet->adc_thd_water_out);
|
|
}
|
|
|
|
static void s5m3700x_hp_trim_initialize(struct s5m3700x_priv *s5m3700x)
|
|
{
|
|
u8 temp[50];
|
|
unsigned int i = 0;
|
|
|
|
dev_info(s5m3700x->dev, "%s called, setting hp trim values.\n", __func__);
|
|
|
|
for (i = 0; i < 50; i++) {
|
|
/* Reading HP Normal Trim */
|
|
s5m3700x_i2c_read_reg(S5M3700X_OTP_ADDR, i, &temp[i]);
|
|
s5m3700x->hp_normal_trim[i] = (unsigned int)temp[i];
|
|
|
|
/* Setting HP UHQA Trim */
|
|
s5m3700x->hp_uhqa_trim[i] = ((s5m3700x->hp_normal_trim[i] * 777) + 500) / 1000;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void s5m3700x_jack_register_initialize(struct s5m3700x_priv *s5m3700x)
|
|
{
|
|
dev_info(s5m3700x->dev, "%s called, setting defaults\n", __func__);
|
|
|
|
/* PDB JD CLK Enable */
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_D0_DCTR_CM, 0x01);
|
|
|
|
/* PDB JD Comparator On */
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_C0_ACTR_JD1, 0x03);
|
|
|
|
/* ANT LDET VTH Setting */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_C3_ACTR_JD4,
|
|
CTRV_ANT_LDET_VTH_MASK, ANT_LDET_VTH_0 << CTRV_ANT_LDET_VTH_SHIFT);
|
|
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_D6_DCTR_TEST6, 0x01);
|
|
|
|
/* Tuning Jack DBNC Value */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_D8_DCTR_DBNC1,
|
|
JACK_DBNC_OUT_MASK, 0x02);
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_D9_DCTR_DBNC2,
|
|
JACK_DBNC_INT_OUT_MASK, 0x02);
|
|
|
|
/* ANT MDET OUT Debounce Time */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_DB_DCTR_DBNC4,
|
|
ANT_MDET_DBNC_OUT_MASK, 0x02);
|
|
|
|
/* IMP RAMP */
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E9_DCTR_IMP3, 0x82);
|
|
|
|
/* ODSEL5 */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_B4_ODSEL5,
|
|
EN_DAC1_OUTTIE_MASK|EN_DAC2_OUTTIE_MASK, EN_DAC1_OUTTIE_MASK|EN_DAC2_OUTTIE_MASK);
|
|
|
|
/* HP PULLDN */
|
|
s5m3700x_update_bits(s5m3700x, S5M3700X_ANALOG_ADDR, S5M3700X_13A_POP_HP,
|
|
EN_HP_PDN_MASK, 0);
|
|
|
|
/* LDET ADC Range Setting */
|
|
s5m3700x_write(s5m3700x, S5M3700X_OTP_ADDR, S5M3700X_2E0_JACK_OTP10, 0x4D);
|
|
s5m3700x_write(s5m3700x, S5M3700X_OTP_ADDR, S5M3700X_2E1_JACK_OTP11, 0x60);
|
|
s5m3700x_write(s5m3700x, S5M3700X_OTP_ADDR, S5M3700X_2E2_JACK_OTP12, 0x77);
|
|
|
|
/* IRQ Un-masking */
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_08_IRQ1M, 0x30);
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_09_IRQ2M, 0x03);
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_0A_IRQ3M, 0xEE);
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_0B_IRQ4M, 0xEE);
|
|
|
|
/* 5-Pin Setting */
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_E0_DCTR_FSM1, 0x0A);
|
|
}
|
|
|
|
static void s5m3700x_jack_register_exit(struct snd_soc_component *codec)
|
|
{
|
|
struct s5m3700x_priv *s5m3700x = snd_soc_component_get_drvdata(codec);
|
|
|
|
/* IRQ Masking */
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_08_IRQ1M, 0xFF);
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_09_IRQ2M, 0xFF);
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_0A_IRQ3M, 0xFF);
|
|
s5m3700x_write(s5m3700x, S5M3700X_DIGITAL_ADDR, S5M3700X_0B_IRQ4M, 0xFF);
|
|
}
|
|
|
|
static int return_irq_type(struct s5m3700x_jack *jackdet)
|
|
{
|
|
unsigned int pend1, pend2, pend3, pend4, pend5, pend6, stat1, stat2;
|
|
|
|
pend1 = jackdet->irq_val[0];
|
|
pend2 = jackdet->irq_val[1];
|
|
pend3 = jackdet->irq_val[2];
|
|
pend4 = jackdet->irq_val[3];
|
|
pend5 = jackdet->irq_val[4];
|
|
pend6 = jackdet->irq_val[5];
|
|
stat1 = jackdet->irq_val[6];
|
|
stat2 = jackdet->irq_val[7];
|
|
|
|
if (pend1 & ST_JO_R)
|
|
return IRQ_ST_JACKOUT;
|
|
else if (pend1 & ST_C_JI_R)
|
|
return IRQ_ST_CMP_JACK_IN;
|
|
else if (pend1 & ST_DEC_WTP_R)
|
|
return IRQ_ST_WTJACK_DEC;
|
|
else if (pend1 & ST_WT_JI_R)
|
|
return IRQ_ST_WTJACK_IN;
|
|
else if (pend1 & ST_WT_JO_R)
|
|
return IRQ_ST_WTJACK_OUT;
|
|
else if (pend1 & ST_IMP_CHK_DONE_R)
|
|
return IRQ_ST_IMP_CHK;
|
|
else if (pend2 & ST_DEC_POLE_R)
|
|
return IRQ_ST_JACKDET;
|
|
else if (pend2 & ST_ETC_R)
|
|
return IRQ_ST_WTJACK_ETC;
|
|
else if (pend3 & BTN_DET_R)
|
|
return IRQ_ST_BTN_DET_R;
|
|
else if (pend4 & BTN_DET_F)
|
|
return IRQ_ST_BTN_DET_F;
|
|
else
|
|
return IRQ_ST_ERR;
|
|
}
|
|
|
|
static int s5m3700x_earjack_handler(struct s5m3700x_priv *s5m3700x)
|
|
{
|
|
struct device *dev = s5m3700x->dev;
|
|
struct s5m3700x_jack *jackdet = s5m3700x->p_jackdet;
|
|
struct earjack_state *jackstate = &jackdet->jack_state;
|
|
int irq_type = return_irq_type(jackdet);
|
|
|
|
mutex_lock(&jackdet->key_lock);
|
|
|
|
switch (irq_type) {
|
|
case IRQ_ST_JACKOUT:
|
|
dev_info(dev, "[IRQ] %s Jack out interrupt, line: %d\n",
|
|
__func__, __LINE__);
|
|
s5m3700x_jackstate_set(jackdet, JACK_OUT);
|
|
|
|
cancel_delayed_work(&jackdet->ldet_chk_work);
|
|
cancel_delayed_work(&jackdet->jack_det_work);
|
|
queue_delayed_work(jackdet->jack_det_wq, &jackdet->jack_det_work,
|
|
msecs_to_jiffies(0));
|
|
|
|
queue_delayed_work(jackdet->buttons_release_wq, &jackdet->buttons_work,
|
|
msecs_to_jiffies(jackdet->btn_adc_delay));
|
|
break;
|
|
case IRQ_ST_CMP_JACK_IN:
|
|
dev_info(dev, "[IRQ] %s Completely Jack in interrupt, line: %d\n",
|
|
__func__, __LINE__);
|
|
s5m3700x_jackstate_set(jackdet, JACK_CMP);
|
|
break;
|
|
case IRQ_ST_WTJACK_DEC:
|
|
if (jackstate->cur_jack_state == JACK_WTP_DEC) {
|
|
dev_info(dev, "[IRQ] %s IRQ_ST_WTJACK_DEC is already on going : %d\n",
|
|
__func__, __LINE__);
|
|
break;
|
|
}
|
|
dev_info(dev, "[IRQ] %s Water Protection Decision interrupt, line: %d\n",
|
|
__func__, __LINE__);
|
|
s5m3700x_jackstate_set(jackdet, JACK_WTP_DEC);
|
|
/* lock for jack and button irq */
|
|
jack_wake_lock(jackdet->jack_wakeup);
|
|
|
|
/* run ldet adc workqueue */
|
|
cancel_delayed_work(&jackdet->ldet_chk_work);
|
|
queue_delayed_work(jackdet->ldet_chk_wq, &jackdet->ldet_chk_work,
|
|
msecs_to_jiffies(jackdet->ldet_chk_delay));
|
|
break;
|
|
case IRQ_ST_WTJACK_IN:
|
|
case IRQ_ST_WTJACK_OUT:
|
|
if (irq_type == IRQ_ST_WTJACK_IN)
|
|
dev_info(dev, "[IRQ] %s IRQ_ST_WTJACK_IN, line: %d\n",
|
|
__func__, __LINE__);
|
|
else
|
|
dev_info(dev, "[IRQ] %s IRQ_ST_WTJACK_OUT, line: %d\n",
|
|
__func__, __LINE__);
|
|
s5m3700x_jackstate_set(jackdet, JACK_WTP_JIO);
|
|
break;
|
|
case IRQ_ST_IMP_CHK:
|
|
dev_info(dev, "[IRQ] %s IMP interrupt, line: %d\n",
|
|
__func__, __LINE__);
|
|
s5m3700x_jackstate_set(jackdet, JACK_IMP);
|
|
break;
|
|
case IRQ_ST_JACKDET:
|
|
dev_info(dev, "[IRQ] %s Jack det interrupt, line: %d\n",
|
|
__func__, __LINE__);
|
|
|
|
if (jackdet->earjack_re_read == 1) {
|
|
jackdet->earjack_re_read = 0;
|
|
break;
|
|
}
|
|
|
|
s5m3700x_jackstate_set(jackdet, JACK_POLE_DEC);
|
|
|
|
/* lock for jack and button irq */
|
|
jack_wake_lock(jackdet->jack_wakeup);
|
|
|
|
cancel_delayed_work(&jackdet->jack_det_work);
|
|
queue_delayed_work(jackdet->jack_det_wq, &jackdet->jack_det_work,
|
|
msecs_to_jiffies(jackdet->mdet_delay));
|
|
break;
|
|
case IRQ_ST_WTJACK_ETC:
|
|
dev_info(dev, "[IRQ] %s State WTP Aux interrupt, line: %d\n",
|
|
__func__, __LINE__);
|
|
|
|
/* lock for jack and button irq */
|
|
jack_wake_lock(jackdet->jack_wakeup);
|
|
|
|
cancel_delayed_work(&jackdet->jack_det_work);
|
|
queue_delayed_work(jackdet->jack_det_wq, &jackdet->jack_det_work,
|
|
msecs_to_jiffies(jackdet->mdet_delay));
|
|
break;
|
|
case IRQ_ST_BTN_DET_R:
|
|
dev_info(dev, "[IRQ] %s Button Press interrupt, line: %d\n",
|
|
__func__, __LINE__);
|
|
/* lock for jack and button irq */
|
|
jack_wake_lock(jackdet->jack_wakeup);
|
|
|
|
jackstate->cur_btn_state = BUTTON_PRESS;
|
|
queue_delayed_work(jackdet->buttons_press_wq, &jackdet->buttons_work,
|
|
msecs_to_jiffies(jackdet->btn_adc_delay));
|
|
break;
|
|
case IRQ_ST_BTN_DET_F:
|
|
dev_info(dev, "[IRQ] %s Button Release interrupt, line: %d\n",
|
|
__func__, __LINE__);
|
|
/* lock for jack and button irq */
|
|
jack_wake_lock(jackdet->jack_wakeup);
|
|
|
|
jackstate->cur_btn_state = BUTTON_RELEASE;
|
|
queue_delayed_work(jackdet->buttons_release_wq, &jackdet->buttons_work,
|
|
msecs_to_jiffies(jackdet->btn_adc_delay));
|
|
break;
|
|
default:
|
|
dev_info(dev, "[IRQ] %s IRQ return type skip, line %d\n",
|
|
__func__, __LINE__);
|
|
break;
|
|
}
|
|
|
|
mutex_unlock(&jackdet->key_lock);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* s5m3700x_notifier_handler() - Codec IRQ Handler
|
|
*
|
|
* Desc: Set codec register according to codec IRQ.
|
|
*/
|
|
static int s5m3700x_notifier_handler(struct notifier_block *nb,
|
|
unsigned long insert, void *data)
|
|
{
|
|
struct s5m3700x_notifier_struct *ns = data;
|
|
struct s5m3700x_priv *s5m3700x = ns->s5m3700x;
|
|
|
|
dev_err(s5m3700x->dev, "%s called. insert: %d\n", __func__, insert);
|
|
|
|
s5m3700x_earjack_handler(s5m3700x);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t s5m3700x_irq_thread(int irq, void *irq_data)
|
|
{
|
|
struct s5m3700x_jack *jackdet = irq_data;
|
|
struct s5m3700x_priv *s5m3700x = jackdet->p_s5m3700x;
|
|
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_01_IRQ1, &jackdet->irq_val[0]);
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_02_IRQ2, &jackdet->irq_val[1]);
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_03_IRQ3, &jackdet->irq_val[2]);
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_04_IRQ4, &jackdet->irq_val[3]);
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_05_IRQ5, &jackdet->irq_val[4]);
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_06_IRQ6, &jackdet->irq_val[5]);
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_F0_STATUS1, &jackdet->irq_val[6]);
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_F1_STATUS2, &jackdet->irq_val[7]);
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_F2_STATUS3, &jackdet->irq_val[8]);
|
|
s5m3700x_i2c_read_reg(S5M3700X_DIGITAL_ADDR, S5M3700X_FA_STATUS11, &jackdet->irq_val[9]);
|
|
|
|
dev_info(s5m3700x->dev,
|
|
"[IRQ] %s(%d) 0x1:%02x 0x2:%02x 0x3:%02x 0x4:%02x 0x5:%02x 0x6:%02x st1:%02x st2:%02x st3:%02x st4:%02x\n",
|
|
__func__, __LINE__, jackdet->irq_val[0], jackdet->irq_val[1],
|
|
jackdet->irq_val[2], jackdet->irq_val[3], jackdet->irq_val[4],
|
|
jackdet->irq_val[5], jackdet->irq_val[6], jackdet->irq_val[7], jackdet->irq_val[8], jackdet->irq_val[9]);
|
|
|
|
blocking_notifier_call_chain(&s5m3700x_notifier, 0, &s5m3700x_notifier_t);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* s5m3700x_register_inputdev() - Register button input device
|
|
*
|
|
* @jackdet: jack information struct
|
|
*
|
|
* Desc: Register button input device
|
|
*/
|
|
static int s5m3700x_register_inputdev(struct s5m3700x_jack *jackdet)
|
|
{
|
|
struct device *dev = jackdet->p_s5m3700x->dev;
|
|
int i, ret;
|
|
|
|
/* Register Headset Device */
|
|
jackdet->input = devm_input_allocate_device(dev);
|
|
if (!jackdet->input) {
|
|
dev_err(dev, "Failed to allocate switch input device\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
jackdet->input->name = "S5M3700X Headset Input";
|
|
jackdet->input->phys = dev_name(dev);
|
|
jackdet->input->id.bustype = BUS_I2C;
|
|
|
|
ret = input_register_device(jackdet->input);
|
|
if (ret != 0) {
|
|
jackdet->input = NULL;
|
|
dev_err(dev, "Failed to register switch input device\n");
|
|
}
|
|
|
|
/*
|
|
* input_set_capability (dev, type, code)
|
|
* @dev : input device
|
|
* @type : event type (EV_KEY, EV_SW, etc...)
|
|
* @code : event code (4POLE, 3POLE, LINE...)
|
|
*/
|
|
/* 3-Pole event */
|
|
input_set_capability(jackdet->input, EV_SW, SW_HEADPHONE_INSERT);
|
|
/* 4-Pole event */
|
|
input_set_capability(jackdet->input, EV_SW, SW_MICROPHONE_INSERT);
|
|
/* Button event */
|
|
for (i = 0; i < 4; i++)
|
|
input_set_capability(jackdet->input, EV_KEY,
|
|
jackdet->jack_buttons_zones[i].code);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* s5m3700x_jack_probe() - Initialize variable related jack
|
|
*
|
|
* @codec: SoC audio codec device
|
|
*
|
|
* Desc: This function is called by s5m3700x_component_probe. For separate codec
|
|
* and jack code, this function called from codec driver. This function initialize
|
|
* jack variable, workqueue, mutex, and wakelock.
|
|
*/
|
|
|
|
int s5m3700x_jack_probe(struct s5m3700x_priv *s5m3700x)
|
|
{
|
|
struct s5m3700x_jack *jackdet;
|
|
struct earjack_state *jackstate;
|
|
int ret;
|
|
|
|
dev_info(s5m3700x->dev, "Codec Jack Probe: (%s)\n", __func__);
|
|
|
|
jackdet = kzalloc(sizeof(struct s5m3700x_jack), GFP_KERNEL);
|
|
if (jackdet == NULL)
|
|
return -ENOMEM;
|
|
|
|
jackdet->codec = s5m3700x->codec;
|
|
jackdet->p_s5m3700x = s5m3700x;
|
|
s5m3700x->p_jackdet = jackdet;
|
|
jackstate = &jackdet->jack_state;
|
|
|
|
/* Initialize workqueue */
|
|
/* Initiallize workqueue for jack detect handling */
|
|
INIT_DELAYED_WORK(&jackdet->jack_det_work, s5m3700x_jack_det_work);
|
|
jackdet->jack_det_wq = create_singlethread_workqueue("jack_det_wq");
|
|
if (jackdet->jack_det_wq == NULL) {
|
|
dev_err(s5m3700x->dev, "Failed to create jack_det_wq\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Initialize workqueue for button handling */
|
|
INIT_DELAYED_WORK(&jackdet->buttons_work, s5m3700x_buttons_work);
|
|
jackdet->buttons_press_wq = create_singlethread_workqueue("buttons_press_wq");
|
|
if (jackdet->buttons_press_wq == NULL) {
|
|
dev_err(s5m3700x->dev, "Failed to create buttons_press_wq\n");
|
|
return -ENOMEM;
|
|
}
|
|
jackdet->buttons_release_wq = create_singlethread_workqueue("buttons_release_wq");
|
|
if (jackdet->buttons_release_wq == NULL) {
|
|
dev_err(s5m3700x->dev, "Failed to create buttons_release_wq\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Initialize workqueue for ldet detect handling */
|
|
INIT_DELAYED_WORK(&jackdet->ldet_chk_work, s5m3700x_ldet_chk_work);
|
|
jackdet->ldet_chk_wq = create_singlethread_workqueue("ldet_chk_wq");
|
|
if (jackdet->ldet_chk_wq == NULL) {
|
|
dev_err(s5m3700x->dev, "Failed to create ldet_chk_wq\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Initialize mutex lock */
|
|
mutex_init(&jackdet->key_lock);
|
|
mutex_init(&jackdet->mdet_lock);
|
|
mutex_init(&jackdet->adc_lock);
|
|
if (jack_set_wake_lock(jackdet) < 0) {
|
|
pr_err("%s: jack_set_wake_lock fail\n", __func__);
|
|
goto err_wake_lock;
|
|
}
|
|
|
|
/* Device Tree for jack */
|
|
s5m3700x_jack_parse_dt(s5m3700x);
|
|
|
|
/* Register input device */
|
|
s5m3700x_register_inputdev(jackdet);
|
|
|
|
/* Jack Notifier initialize */
|
|
s5m3700x_notifier_block.notifier_call = s5m3700x_notifier_handler,
|
|
s5m3700x_register_notifier(&s5m3700x_notifier_block, s5m3700x);
|
|
|
|
/* Request Jack IRQ */
|
|
if (jackdet->irqb_gpio > 0) {
|
|
jackdet->codec_irq = gpio_to_irq(jackdet->irqb_gpio);
|
|
dev_info(s5m3700x->dev, "%s: codec_irq = %d\n", __func__, jackdet->codec_irq);
|
|
if (jackdet->codec_irq > 0) {
|
|
ret = request_threaded_irq(jackdet->codec_irq,
|
|
NULL, s5m3700x_irq_thread,
|
|
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
|
"s5m3700x-irq", jackdet);
|
|
if (ret)
|
|
dev_err(s5m3700x->dev, "%s Failed to Request IRQ. ret: %d\n", __func__, ret);
|
|
else {
|
|
dev_info(s5m3700x->dev, "%s Request IRQ. ret: %d\n", __func__, ret);
|
|
|
|
ret = enable_irq_wake(jackdet->codec_irq);
|
|
if (ret < 0) {
|
|
dev_err(s5m3700x->dev, "%s Failed to Enable Wakeup Source(%d)\n", __func__, ret);
|
|
goto err_wake_irq;
|
|
}
|
|
}
|
|
} else
|
|
dev_err(s5m3700x->dev, "%s Failed gpio_to_irq. ret: %d\n", __func__, jackdet->codec_irq);
|
|
}
|
|
|
|
/* Initialize jack state variable */
|
|
jackdet->earjack_re_read = 0;
|
|
jackdet->btn_state = 0;
|
|
jackstate->ldet_adc = -EINVAL;
|
|
jackstate->mdet_adc = -EINVAL;
|
|
jackstate->prv_btn_state = BUTTON_RELEASE;
|
|
jackstate->cur_btn_state = BUTTON_RELEASE;
|
|
jackstate->button_code = -EINVAL;
|
|
jackstate->btn_adc = -EINVAL;
|
|
|
|
#if IS_ENABLED(CONFIG_PM)
|
|
pm_runtime_get_sync(s5m3700x->dev);
|
|
#else
|
|
s5m3700x_enable(s5m3700x->dev);
|
|
#endif
|
|
|
|
/* register value init for jack */
|
|
jackstate->prv_jack_state = JACK_OUT;
|
|
jackstate->cur_jack_state = JACK_OUT;
|
|
|
|
s5m3700x_jack_register_initialize(s5m3700x);
|
|
|
|
if (s5m3700x->codec_ver >= REV_0_3)
|
|
s5m3700x_hp_trim_initialize(s5m3700x);
|
|
|
|
/* Configure mic bias voltage */
|
|
s5m3700x_configure_mic_bias(jackdet);
|
|
|
|
#if IS_ENABLED(CONFIG_PM)
|
|
pm_runtime_put_sync(s5m3700x->dev);
|
|
#else
|
|
s5m3700x_disable(s5m3700x->dev);
|
|
#endif
|
|
|
|
/* Jack OTP Read */
|
|
s5m3700x_read_imp_otp(jackdet);
|
|
|
|
return true;
|
|
err_wake_irq:
|
|
disable_irq_wake(jackdet->codec_irq);
|
|
err_wake_lock:
|
|
wakeup_source_unregister(jackdet->jack_wakeup);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* s5m3700x_jack_remove() - Remove variable related jack
|
|
*
|
|
* @codec: SoC audio codec device
|
|
*
|
|
* Desc: This function is called by s5m3700x_component_remove. For separate codec
|
|
* and jack code, this function called from codec driver. This function destroy
|
|
* workqueue.
|
|
*/
|
|
int s5m3700x_jack_remove(struct snd_soc_component *codec)
|
|
{
|
|
struct s5m3700x_priv *s5m3700x = snd_soc_component_get_drvdata(codec);
|
|
struct s5m3700x_jack *jackdet = s5m3700x->p_jackdet;
|
|
|
|
dev_info(codec->dev, "(*) %s called\n", __func__);
|
|
|
|
destroy_workqueue(jackdet->jack_det_wq);
|
|
destroy_workqueue(jackdet->buttons_press_wq);
|
|
destroy_workqueue(jackdet->buttons_release_wq);
|
|
destroy_workqueue(jackdet->ldet_chk_wq);
|
|
|
|
s5m3700x_jack_register_exit(codec);
|
|
|
|
/* Unregister ADC pin */
|
|
kfree(jackdet);
|
|
|
|
return true;
|
|
}
|