diff options
Diffstat (limited to 'sound/soc/codecs/wm_hubs.c')
| -rw-r--r-- | sound/soc/codecs/wm_hubs.c | 220 | 
1 files changed, 195 insertions, 25 deletions
diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index 6c028c47060..dfe957a47f2 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -109,12 +109,103 @@ irqreturn_t wm_hubs_dcs_done(int irq, void *data)  }  EXPORT_SYMBOL_GPL(wm_hubs_dcs_done); +static bool wm_hubs_dac_hp_direct(struct snd_soc_codec *codec) +{ +	int reg; + +	/* If we're going via the mixer we'll need to do additional checks */ +	reg = snd_soc_read(codec, WM8993_OUTPUT_MIXER1); +	if (!(reg & WM8993_DACL_TO_HPOUT1L)) { +		if (reg & ~WM8993_DACL_TO_MIXOUTL) { +			dev_vdbg(codec->dev, "Analogue paths connected: %x\n", +				 reg & ~WM8993_DACL_TO_HPOUT1L); +			return false; +		} else { +			dev_vdbg(codec->dev, "HPL connected to mixer\n"); +		} +	} else { +		dev_vdbg(codec->dev, "HPL connected to DAC\n"); +	} + +	reg = snd_soc_read(codec, WM8993_OUTPUT_MIXER2); +	if (!(reg & WM8993_DACR_TO_HPOUT1R)) { +		if (reg & ~WM8993_DACR_TO_MIXOUTR) { +			dev_vdbg(codec->dev, "Analogue paths connected: %x\n", +				 reg & ~WM8993_DACR_TO_HPOUT1R); +			return false; +		} else { +			dev_vdbg(codec->dev, "HPR connected to mixer\n"); +		} +	} else { +		dev_vdbg(codec->dev, "HPR connected to DAC\n"); +	} + +	return true; +} + +struct wm_hubs_dcs_cache { +	struct list_head list; +	unsigned int left; +	unsigned int right; +	u16 dcs_cfg; +}; + +static bool wm_hubs_dcs_cache_get(struct snd_soc_codec *codec, +				  struct wm_hubs_dcs_cache **entry) +{ +	struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); +	struct wm_hubs_dcs_cache *cache; +	unsigned int left, right; + +	left = snd_soc_read(codec, WM8993_LEFT_OUTPUT_VOLUME); +	left &= WM8993_HPOUT1L_VOL_MASK; + +	right = snd_soc_read(codec, WM8993_RIGHT_OUTPUT_VOLUME); +	right &= WM8993_HPOUT1R_VOL_MASK; + +	list_for_each_entry(cache, &hubs->dcs_cache, list) { +		if (cache->left != left || cache->right != right) +			continue; + +		*entry = cache; +		return true; +	} + +	return false; +} + +static void wm_hubs_dcs_cache_set(struct snd_soc_codec *codec, u16 dcs_cfg) +{ +	struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); +	struct wm_hubs_dcs_cache *cache; + +	if (hubs->no_cache_dac_hp_direct) +		return; + +	cache = devm_kzalloc(codec->dev, sizeof(*cache), GFP_KERNEL); +	if (!cache) { +		dev_err(codec->dev, "Failed to allocate DCS cache entry\n"); +		return; +	} + +	cache->left = snd_soc_read(codec, WM8993_LEFT_OUTPUT_VOLUME); +	cache->left &= WM8993_HPOUT1L_VOL_MASK; + +	cache->right = snd_soc_read(codec, WM8993_RIGHT_OUTPUT_VOLUME); +	cache->right &= WM8993_HPOUT1R_VOL_MASK; + +	cache->dcs_cfg = dcs_cfg; + +	list_add_tail(&cache->list, &hubs->dcs_cache); +} +  /*   * Startup calibration of the DC servo   */  static void calibrate_dc_servo(struct snd_soc_codec *codec)  {  	struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); +	struct wm_hubs_dcs_cache *cache;  	s8 offset;  	u16 reg, reg_l, reg_r, dcs_cfg, dcs_reg; @@ -129,10 +220,11 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)  	/* If we're using a digital only path and have a previously  	 * callibrated DC servo offset stored then use that. */ -	if (hubs->class_w && hubs->class_w_dcs) { -		dev_dbg(codec->dev, "Using cached DC servo offset %x\n", -			hubs->class_w_dcs); -		snd_soc_write(codec, dcs_reg, hubs->class_w_dcs); +	if (wm_hubs_dac_hp_direct(codec) && +	    wm_hubs_dcs_cache_get(codec, &cache)) { +		dev_dbg(codec->dev, "Using cached DCS offset %x for %d,%d\n", +			cache->dcs_cfg, cache->left, cache->right); +		snd_soc_write(codec, dcs_reg, cache->dcs_cfg);  		wait_for_dc_servo(codec,  				  WM8993_DCS_TRIG_DAC_WR_0 |  				  WM8993_DCS_TRIG_DAC_WR_1); @@ -207,8 +299,8 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)  	/* Save the callibrated offset if we're in class W mode and  	 * therefore don't have any analogue signal mixed in. */ -	if (hubs->class_w && !hubs->no_cache_class_w) -		hubs->class_w_dcs = dcs_cfg; +	if (wm_hubs_dac_hp_direct(codec)) +		wm_hubs_dcs_cache_set(codec, dcs_cfg);  }  /* @@ -223,9 +315,6 @@ static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol,  	ret = snd_soc_put_volsw(kcontrol, ucontrol); -	/* Updating the analogue gains invalidates the DC servo cache */ -	hubs->class_w_dcs = 0; -  	/* If we're applying an offset correction then updating the  	 * callibration would be likely to introduce further offsets. */  	if (hubs->dcs_codes_l || hubs->dcs_codes_r || hubs->no_series_update) @@ -530,6 +619,86 @@ static int lineout_event(struct snd_soc_dapm_widget *w,  	return 0;  } +void wm_hubs_update_class_w(struct snd_soc_codec *codec) +{ +	struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); +	int enable = WM8993_CP_DYN_V | WM8993_CP_DYN_FREQ; + +	if (!wm_hubs_dac_hp_direct(codec)) +		enable = false; + +	if (hubs->check_class_w_digital && !hubs->check_class_w_digital(codec)) +		enable = false; + +	dev_vdbg(codec->dev, "Class W %s\n", enable ? "enabled" : "disabled"); + +	snd_soc_update_bits(codec, WM8993_CLASS_W_0, +			    WM8993_CP_DYN_V | WM8993_CP_DYN_FREQ, enable); +} +EXPORT_SYMBOL_GPL(wm_hubs_update_class_w); + +#define WM_HUBS_SINGLE_W(xname, reg, shift, max, invert) \ +{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ +	.info = snd_soc_info_volsw, \ +	.get = snd_soc_dapm_get_volsw, .put = class_w_put_volsw, \ +	.private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) } + +static int class_w_put_volsw(struct snd_kcontrol *kcontrol, +			      struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); +	struct snd_soc_dapm_widget *widget = wlist->widgets[0]; +	struct snd_soc_codec *codec = widget->codec; +	int ret; + +	ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + +	wm_hubs_update_class_w(codec); + +	return ret; +} + +#define WM_HUBS_ENUM_W(xname, xenum) \ +{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ +	.info = snd_soc_info_enum_double, \ +	.get = snd_soc_dapm_get_enum_double, \ +	.put = class_w_put_double, \ +	.private_value = (unsigned long)&xenum } + +static int class_w_put_double(struct snd_kcontrol *kcontrol, +			      struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); +	struct snd_soc_dapm_widget *widget = wlist->widgets[0]; +	struct snd_soc_codec *codec = widget->codec; +	int ret; + +	ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + +	wm_hubs_update_class_w(codec); + +	return ret; +} + +static const char *hp_mux_text[] = { +	"Mixer", +	"DAC", +}; + +static const struct soc_enum hpl_enum = +	SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER1, 8, 2, hp_mux_text); + +const struct snd_kcontrol_new wm_hubs_hpl_mux = +	WM_HUBS_ENUM_W("Left Headphone Mux", hpl_enum); +EXPORT_SYMBOL_GPL(wm_hubs_hpl_mux); + +static const struct soc_enum hpr_enum = +	SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER2, 8, 2, hp_mux_text); + +const struct snd_kcontrol_new wm_hubs_hpr_mux = +	WM_HUBS_ENUM_W("Right Headphone Mux", hpr_enum); +EXPORT_SYMBOL_GPL(wm_hubs_hpr_mux); +  static const struct snd_kcontrol_new in1l_pga[] = {  SOC_DAPM_SINGLE("IN1LP Switch", WM8993_INPUT_MIXER2, 5, 1, 0),  SOC_DAPM_SINGLE("IN1LN Switch", WM8993_INPUT_MIXER2, 4, 1, 0), @@ -561,25 +730,25 @@ SOC_DAPM_SINGLE("IN1R Switch", WM8993_INPUT_MIXER4, 5, 1, 0),  };  static const struct snd_kcontrol_new left_output_mixer[] = { -SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0), -SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0), -SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0), -SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0), -SOC_DAPM_SINGLE("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0), -SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0), -SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0), -SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0), +WM_HUBS_SINGLE_W("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0), +WM_HUBS_SINGLE_W("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0), +WM_HUBS_SINGLE_W("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0), +WM_HUBS_SINGLE_W("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0), +WM_HUBS_SINGLE_W("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0), +WM_HUBS_SINGLE_W("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0), +WM_HUBS_SINGLE_W("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0), +WM_HUBS_SINGLE_W("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0),  };  static const struct snd_kcontrol_new right_output_mixer[] = { -SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0), -SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0), -SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0), -SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0), -SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0), -SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0), -SOC_DAPM_SINGLE("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0), -SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0), +WM_HUBS_SINGLE_W("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0), +WM_HUBS_SINGLE_W("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0), +WM_HUBS_SINGLE_W("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0), +WM_HUBS_SINGLE_W("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0), +WM_HUBS_SINGLE_W("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0), +WM_HUBS_SINGLE_W("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0), +WM_HUBS_SINGLE_W("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0), +WM_HUBS_SINGLE_W("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0),  };  static const struct snd_kcontrol_new earpiece_mixer[] = { @@ -943,6 +1112,7 @@ int wm_hubs_add_analogue_routes(struct snd_soc_codec *codec,  	struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);  	struct snd_soc_dapm_context *dapm = &codec->dapm; +	INIT_LIST_HEAD(&hubs->dcs_cache);  	init_completion(&hubs->dcs_done);  	snd_soc_dapm_add_routes(dapm, analogue_routes,  |