162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * cx20442.c -- CX20442 ALSA Soc Audio driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Initially based on sound/soc/codecs/wm8400.c 862306a36Sopenharmony_ci * Copyright 2008, 2009 Wolfson Microelectronics PLC. 962306a36Sopenharmony_ci * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/tty.h> 1362306a36Sopenharmony_ci#include <linux/slab.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include <sound/core.h> 1862306a36Sopenharmony_ci#include <sound/initval.h> 1962306a36Sopenharmony_ci#include <sound/soc.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#include "cx20442.h" 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_cistruct cx20442_priv { 2562306a36Sopenharmony_ci struct tty_struct *tty; 2662306a36Sopenharmony_ci struct regulator *por; 2762306a36Sopenharmony_ci u8 reg_cache; 2862306a36Sopenharmony_ci}; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define CX20442_PM 0x0 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#define CX20442_TELIN 0 3362306a36Sopenharmony_ci#define CX20442_TELOUT 1 3462306a36Sopenharmony_ci#define CX20442_MIC 2 3562306a36Sopenharmony_ci#define CX20442_SPKOUT 3 3662306a36Sopenharmony_ci#define CX20442_AGC 4 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic const struct snd_soc_dapm_widget cx20442_dapm_widgets[] = { 3962306a36Sopenharmony_ci SND_SOC_DAPM_OUTPUT("TELOUT"), 4062306a36Sopenharmony_ci SND_SOC_DAPM_OUTPUT("SPKOUT"), 4162306a36Sopenharmony_ci SND_SOC_DAPM_OUTPUT("AGCOUT"), 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci SND_SOC_DAPM_MIXER("SPKOUT Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci SND_SOC_DAPM_PGA("TELOUT Amp", CX20442_PM, CX20442_TELOUT, 0, NULL, 0), 4662306a36Sopenharmony_ci SND_SOC_DAPM_PGA("SPKOUT Amp", CX20442_PM, CX20442_SPKOUT, 0, NULL, 0), 4762306a36Sopenharmony_ci SND_SOC_DAPM_PGA("SPKOUT AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0), 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), 5062306a36Sopenharmony_ci SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci SND_SOC_DAPM_MICBIAS("TELIN Bias", CX20442_PM, CX20442_TELIN, 0), 5562306a36Sopenharmony_ci SND_SOC_DAPM_MICBIAS("MIC Bias", CX20442_PM, CX20442_MIC, 0), 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci SND_SOC_DAPM_PGA("MIC AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0), 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci SND_SOC_DAPM_INPUT("TELIN"), 6062306a36Sopenharmony_ci SND_SOC_DAPM_INPUT("MIC"), 6162306a36Sopenharmony_ci SND_SOC_DAPM_INPUT("AGCIN"), 6262306a36Sopenharmony_ci}; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistatic const struct snd_soc_dapm_route cx20442_audio_map[] = { 6562306a36Sopenharmony_ci {"TELOUT", NULL, "TELOUT Amp"}, 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci {"SPKOUT", NULL, "SPKOUT Mixer"}, 6862306a36Sopenharmony_ci {"SPKOUT Mixer", NULL, "SPKOUT Amp"}, 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci {"TELOUT Amp", NULL, "DAC"}, 7162306a36Sopenharmony_ci {"SPKOUT Amp", NULL, "DAC"}, 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci {"SPKOUT Mixer", NULL, "SPKOUT AGC"}, 7462306a36Sopenharmony_ci {"SPKOUT AGC", NULL, "AGCIN"}, 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci {"AGCOUT", NULL, "MIC AGC"}, 7762306a36Sopenharmony_ci {"MIC AGC", NULL, "MIC"}, 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci {"MIC Bias", NULL, "MIC"}, 8062306a36Sopenharmony_ci {"Input Mixer", NULL, "MIC Bias"}, 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci {"TELIN Bias", NULL, "TELIN"}, 8362306a36Sopenharmony_ci {"Input Mixer", NULL, "TELIN Bias"}, 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci {"ADC", NULL, "Input Mixer"}, 8662306a36Sopenharmony_ci}; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic unsigned int cx20442_read_reg_cache(struct snd_soc_component *component, 8962306a36Sopenharmony_ci unsigned int reg) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci if (reg >= 1) 9462306a36Sopenharmony_ci return -EINVAL; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci return cx20442->reg_cache; 9762306a36Sopenharmony_ci} 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cienum v253_vls { 10062306a36Sopenharmony_ci V253_VLS_NONE = 0, 10162306a36Sopenharmony_ci V253_VLS_T, 10262306a36Sopenharmony_ci V253_VLS_L, 10362306a36Sopenharmony_ci V253_VLS_LT, 10462306a36Sopenharmony_ci V253_VLS_S, 10562306a36Sopenharmony_ci V253_VLS_ST, 10662306a36Sopenharmony_ci V253_VLS_M, 10762306a36Sopenharmony_ci V253_VLS_MST, 10862306a36Sopenharmony_ci V253_VLS_S1, 10962306a36Sopenharmony_ci V253_VLS_S1T, 11062306a36Sopenharmony_ci V253_VLS_MS1T, 11162306a36Sopenharmony_ci V253_VLS_M1, 11262306a36Sopenharmony_ci V253_VLS_M1ST, 11362306a36Sopenharmony_ci V253_VLS_M1S1T, 11462306a36Sopenharmony_ci V253_VLS_H, 11562306a36Sopenharmony_ci V253_VLS_HT, 11662306a36Sopenharmony_ci V253_VLS_MS, 11762306a36Sopenharmony_ci V253_VLS_MS1, 11862306a36Sopenharmony_ci V253_VLS_M1S, 11962306a36Sopenharmony_ci V253_VLS_M1S1, 12062306a36Sopenharmony_ci V253_VLS_TEST, 12162306a36Sopenharmony_ci}; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistatic int cx20442_pm_to_v253_vls(u8 value) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci switch (value & ~(1 << CX20442_AGC)) { 12662306a36Sopenharmony_ci case 0: 12762306a36Sopenharmony_ci return V253_VLS_T; 12862306a36Sopenharmony_ci case (1 << CX20442_SPKOUT): 12962306a36Sopenharmony_ci case (1 << CX20442_MIC): 13062306a36Sopenharmony_ci case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC): 13162306a36Sopenharmony_ci return V253_VLS_M1S1; 13262306a36Sopenharmony_ci case (1 << CX20442_TELOUT): 13362306a36Sopenharmony_ci case (1 << CX20442_TELIN): 13462306a36Sopenharmony_ci case (1 << CX20442_TELOUT) | (1 << CX20442_TELIN): 13562306a36Sopenharmony_ci return V253_VLS_L; 13662306a36Sopenharmony_ci case (1 << CX20442_TELOUT) | (1 << CX20442_MIC): 13762306a36Sopenharmony_ci return V253_VLS_NONE; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci return -EINVAL; 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_cistatic int cx20442_pm_to_v253_vsp(u8 value) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci switch (value & ~(1 << CX20442_AGC)) { 14462306a36Sopenharmony_ci case (1 << CX20442_SPKOUT): 14562306a36Sopenharmony_ci case (1 << CX20442_MIC): 14662306a36Sopenharmony_ci case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC): 14762306a36Sopenharmony_ci return (bool)(value & (1 << CX20442_AGC)); 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci return (value & (1 << CX20442_AGC)) ? -EINVAL : 0; 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic int cx20442_write(struct snd_soc_component *component, unsigned int reg, 15362306a36Sopenharmony_ci unsigned int value) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component); 15662306a36Sopenharmony_ci int vls, vsp, old, len; 15762306a36Sopenharmony_ci char buf[18]; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (reg >= 1) 16062306a36Sopenharmony_ci return -EINVAL; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci /* tty and write pointers required for talking to the modem 16362306a36Sopenharmony_ci * are expected to be set by the line discipline initialization code */ 16462306a36Sopenharmony_ci if (!cx20442->tty || !cx20442->tty->ops->write) 16562306a36Sopenharmony_ci return -EIO; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci old = cx20442->reg_cache; 16862306a36Sopenharmony_ci cx20442->reg_cache = value; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci vls = cx20442_pm_to_v253_vls(value); 17162306a36Sopenharmony_ci if (vls < 0) 17262306a36Sopenharmony_ci return vls; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci vsp = cx20442_pm_to_v253_vsp(value); 17562306a36Sopenharmony_ci if (vsp < 0) 17662306a36Sopenharmony_ci return vsp; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci if ((vls == V253_VLS_T) || 17962306a36Sopenharmony_ci (vls == cx20442_pm_to_v253_vls(old))) { 18062306a36Sopenharmony_ci if (vsp == cx20442_pm_to_v253_vsp(old)) 18162306a36Sopenharmony_ci return 0; 18262306a36Sopenharmony_ci len = snprintf(buf, ARRAY_SIZE(buf), "at+vsp=%d\r", vsp); 18362306a36Sopenharmony_ci } else if (vsp == cx20442_pm_to_v253_vsp(old)) 18462306a36Sopenharmony_ci len = snprintf(buf, ARRAY_SIZE(buf), "at+vls=%d\r", vls); 18562306a36Sopenharmony_ci else 18662306a36Sopenharmony_ci len = snprintf(buf, ARRAY_SIZE(buf), 18762306a36Sopenharmony_ci "at+vls=%d;+vsp=%d\r", vls, vsp); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci if (unlikely(len > (ARRAY_SIZE(buf) - 1))) 19062306a36Sopenharmony_ci return -ENOMEM; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci dev_dbg(component->dev, "%s: %s\n", __func__, buf); 19362306a36Sopenharmony_ci if (cx20442->tty->ops->write(cx20442->tty, buf, len) != len) 19462306a36Sopenharmony_ci return -EIO; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci return 0; 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci/* 20062306a36Sopenharmony_ci * Line discpline related code 20162306a36Sopenharmony_ci * 20262306a36Sopenharmony_ci * Any of the callback functions below can be used in two ways: 20362306a36Sopenharmony_ci * 1) registerd by a machine driver as one of line discipline operations, 20462306a36Sopenharmony_ci * 2) called from a machine's provided line discipline callback function 20562306a36Sopenharmony_ci * in case when extra machine specific code must be run as well. 20662306a36Sopenharmony_ci */ 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci/* Modem init: echo off, digital speaker off, quiet off, voice mode */ 20962306a36Sopenharmony_cistatic const char v253_init[] = "ate0m0q0+fclass=8\r"; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci/* Line discipline .open() */ 21262306a36Sopenharmony_cistatic int v253_open(struct tty_struct *tty) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci int ret, len = strlen(v253_init); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci /* Doesn't make sense without write callback */ 21762306a36Sopenharmony_ci if (!tty->ops->write) 21862306a36Sopenharmony_ci return -EINVAL; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci /* Won't work if no codec pointer has been passed by a card driver */ 22162306a36Sopenharmony_ci if (!tty->disc_data) 22262306a36Sopenharmony_ci return -ENODEV; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci tty->receive_room = 16; 22562306a36Sopenharmony_ci if (tty->ops->write(tty, v253_init, len) != len) { 22662306a36Sopenharmony_ci ret = -EIO; 22762306a36Sopenharmony_ci goto err; 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci /* Actual setup will be performed after the modem responds. */ 23062306a36Sopenharmony_ci return 0; 23162306a36Sopenharmony_cierr: 23262306a36Sopenharmony_ci tty->disc_data = NULL; 23362306a36Sopenharmony_ci return ret; 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci/* Line discipline .close() */ 23762306a36Sopenharmony_cistatic void v253_close(struct tty_struct *tty) 23862306a36Sopenharmony_ci{ 23962306a36Sopenharmony_ci struct snd_soc_component *component = tty->disc_data; 24062306a36Sopenharmony_ci struct cx20442_priv *cx20442; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci tty->disc_data = NULL; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci if (!component) 24562306a36Sopenharmony_ci return; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci cx20442 = snd_soc_component_get_drvdata(component); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci /* Prevent the codec driver from further accessing the modem */ 25062306a36Sopenharmony_ci cx20442->tty = NULL; 25162306a36Sopenharmony_ci component->card->pop_time = 0; 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci/* Line discipline .hangup() */ 25562306a36Sopenharmony_cistatic void v253_hangup(struct tty_struct *tty) 25662306a36Sopenharmony_ci{ 25762306a36Sopenharmony_ci v253_close(tty); 25862306a36Sopenharmony_ci} 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci/* Line discipline .receive_buf() */ 26162306a36Sopenharmony_cistatic void v253_receive(struct tty_struct *tty, const u8 *cp, const u8 *fp, 26262306a36Sopenharmony_ci size_t count) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci struct snd_soc_component *component = tty->disc_data; 26562306a36Sopenharmony_ci struct cx20442_priv *cx20442; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci if (!component) 26862306a36Sopenharmony_ci return; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci cx20442 = snd_soc_component_get_drvdata(component); 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci if (!cx20442->tty) { 27362306a36Sopenharmony_ci /* First modem response, complete setup procedure */ 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci /* Set up codec driver access to modem controls */ 27662306a36Sopenharmony_ci cx20442->tty = tty; 27762306a36Sopenharmony_ci component->card->pop_time = 1; 27862306a36Sopenharmony_ci } 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cistruct tty_ldisc_ops v253_ops = { 28262306a36Sopenharmony_ci .name = "cx20442", 28362306a36Sopenharmony_ci .owner = THIS_MODULE, 28462306a36Sopenharmony_ci .open = v253_open, 28562306a36Sopenharmony_ci .close = v253_close, 28662306a36Sopenharmony_ci .hangup = v253_hangup, 28762306a36Sopenharmony_ci .receive_buf = v253_receive, 28862306a36Sopenharmony_ci}; 28962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(v253_ops); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci/* 29362306a36Sopenharmony_ci * Codec DAI 29462306a36Sopenharmony_ci */ 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_cistatic struct snd_soc_dai_driver cx20442_dai = { 29762306a36Sopenharmony_ci .name = "cx20442-voice", 29862306a36Sopenharmony_ci .playback = { 29962306a36Sopenharmony_ci .stream_name = "Playback", 30062306a36Sopenharmony_ci .channels_min = 1, 30162306a36Sopenharmony_ci .channels_max = 1, 30262306a36Sopenharmony_ci .rates = SNDRV_PCM_RATE_8000, 30362306a36Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, 30462306a36Sopenharmony_ci }, 30562306a36Sopenharmony_ci .capture = { 30662306a36Sopenharmony_ci .stream_name = "Capture", 30762306a36Sopenharmony_ci .channels_min = 1, 30862306a36Sopenharmony_ci .channels_max = 1, 30962306a36Sopenharmony_ci .rates = SNDRV_PCM_RATE_8000, 31062306a36Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, 31162306a36Sopenharmony_ci }, 31262306a36Sopenharmony_ci}; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_cistatic int cx20442_set_bias_level(struct snd_soc_component *component, 31562306a36Sopenharmony_ci enum snd_soc_bias_level level) 31662306a36Sopenharmony_ci{ 31762306a36Sopenharmony_ci struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component); 31862306a36Sopenharmony_ci int err = 0; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci switch (level) { 32162306a36Sopenharmony_ci case SND_SOC_BIAS_PREPARE: 32262306a36Sopenharmony_ci if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_STANDBY) 32362306a36Sopenharmony_ci break; 32462306a36Sopenharmony_ci if (IS_ERR(cx20442->por)) 32562306a36Sopenharmony_ci err = PTR_ERR(cx20442->por); 32662306a36Sopenharmony_ci else 32762306a36Sopenharmony_ci err = regulator_enable(cx20442->por); 32862306a36Sopenharmony_ci break; 32962306a36Sopenharmony_ci case SND_SOC_BIAS_STANDBY: 33062306a36Sopenharmony_ci if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_PREPARE) 33162306a36Sopenharmony_ci break; 33262306a36Sopenharmony_ci if (IS_ERR(cx20442->por)) 33362306a36Sopenharmony_ci err = PTR_ERR(cx20442->por); 33462306a36Sopenharmony_ci else 33562306a36Sopenharmony_ci err = regulator_disable(cx20442->por); 33662306a36Sopenharmony_ci break; 33762306a36Sopenharmony_ci default: 33862306a36Sopenharmony_ci break; 33962306a36Sopenharmony_ci } 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci return err; 34262306a36Sopenharmony_ci} 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_cistatic int cx20442_component_probe(struct snd_soc_component *component) 34562306a36Sopenharmony_ci{ 34662306a36Sopenharmony_ci struct cx20442_priv *cx20442; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci cx20442 = kzalloc(sizeof(struct cx20442_priv), GFP_KERNEL); 34962306a36Sopenharmony_ci if (cx20442 == NULL) 35062306a36Sopenharmony_ci return -ENOMEM; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci cx20442->por = regulator_get(component->dev, "POR"); 35362306a36Sopenharmony_ci if (IS_ERR(cx20442->por)) { 35462306a36Sopenharmony_ci int err = PTR_ERR(cx20442->por); 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci dev_warn(component->dev, "failed to get POR supply (%d)", err); 35762306a36Sopenharmony_ci /* 35862306a36Sopenharmony_ci * When running on a non-dt platform and requested regulator 35962306a36Sopenharmony_ci * is not available, regulator_get() never returns 36062306a36Sopenharmony_ci * -EPROBE_DEFER as it is not able to justify if the regulator 36162306a36Sopenharmony_ci * may still appear later. On the other hand, the board can 36262306a36Sopenharmony_ci * still set full constraints flag at late_initcall in order 36362306a36Sopenharmony_ci * to instruct regulator_get() to return a dummy one if 36462306a36Sopenharmony_ci * sufficient. Hence, if we get -ENODEV here, let's convert 36562306a36Sopenharmony_ci * it to -EPROBE_DEFER and wait for the board to decide or 36662306a36Sopenharmony_ci * let Deferred Probe infrastructure handle this error. 36762306a36Sopenharmony_ci */ 36862306a36Sopenharmony_ci if (err == -ENODEV) 36962306a36Sopenharmony_ci err = -EPROBE_DEFER; 37062306a36Sopenharmony_ci kfree(cx20442); 37162306a36Sopenharmony_ci return err; 37262306a36Sopenharmony_ci } 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci cx20442->tty = NULL; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci snd_soc_component_set_drvdata(component, cx20442); 37762306a36Sopenharmony_ci component->card->pop_time = 0; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci return 0; 38062306a36Sopenharmony_ci} 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci/* power down chip */ 38362306a36Sopenharmony_cistatic void cx20442_component_remove(struct snd_soc_component *component) 38462306a36Sopenharmony_ci{ 38562306a36Sopenharmony_ci struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component); 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci if (cx20442->tty) { 38862306a36Sopenharmony_ci struct tty_struct *tty = cx20442->tty; 38962306a36Sopenharmony_ci tty_hangup(tty); 39062306a36Sopenharmony_ci } 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci if (!IS_ERR(cx20442->por)) { 39362306a36Sopenharmony_ci /* should be already in STANDBY, hence disabled */ 39462306a36Sopenharmony_ci regulator_put(cx20442->por); 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci snd_soc_component_set_drvdata(component, NULL); 39862306a36Sopenharmony_ci kfree(cx20442); 39962306a36Sopenharmony_ci} 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_cistatic const struct snd_soc_component_driver cx20442_component_dev = { 40262306a36Sopenharmony_ci .probe = cx20442_component_probe, 40362306a36Sopenharmony_ci .remove = cx20442_component_remove, 40462306a36Sopenharmony_ci .set_bias_level = cx20442_set_bias_level, 40562306a36Sopenharmony_ci .read = cx20442_read_reg_cache, 40662306a36Sopenharmony_ci .write = cx20442_write, 40762306a36Sopenharmony_ci .dapm_widgets = cx20442_dapm_widgets, 40862306a36Sopenharmony_ci .num_dapm_widgets = ARRAY_SIZE(cx20442_dapm_widgets), 40962306a36Sopenharmony_ci .dapm_routes = cx20442_audio_map, 41062306a36Sopenharmony_ci .num_dapm_routes = ARRAY_SIZE(cx20442_audio_map), 41162306a36Sopenharmony_ci .idle_bias_on = 1, 41262306a36Sopenharmony_ci .use_pmdown_time = 1, 41362306a36Sopenharmony_ci .endianness = 1, 41462306a36Sopenharmony_ci}; 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_cistatic int cx20442_platform_probe(struct platform_device *pdev) 41762306a36Sopenharmony_ci{ 41862306a36Sopenharmony_ci return devm_snd_soc_register_component(&pdev->dev, 41962306a36Sopenharmony_ci &cx20442_component_dev, &cx20442_dai, 1); 42062306a36Sopenharmony_ci} 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_cistatic struct platform_driver cx20442_platform_driver = { 42362306a36Sopenharmony_ci .driver = { 42462306a36Sopenharmony_ci .name = "cx20442-codec", 42562306a36Sopenharmony_ci }, 42662306a36Sopenharmony_ci .probe = cx20442_platform_probe, 42762306a36Sopenharmony_ci}; 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_cimodule_platform_driver(cx20442_platform_driver); 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ciMODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver"); 43262306a36Sopenharmony_ciMODULE_AUTHOR("Janusz Krzysztofik"); 43362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 43462306a36Sopenharmony_ciMODULE_ALIAS("platform:cx20442-codec"); 435