summaryrefslogtreecommitdiff
path: root/sound/soc/codecs/twl6040.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-05-23 13:05:43 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2012-05-23 13:05:43 -0700
commit2e341ca686042aa464efa755447e7bcee91d1eb6 (patch)
treec6b16b6b6a6e871fa04396cb2c7eb759bcad5be3 /sound/soc/codecs/twl6040.c
parent927ad551031798d4cba49766549600bbb33872d7 (diff)
parent85e184e4c3cd3e2285ceab91ff8f0cac094e8a85 (diff)
Merge tag 'sound-3.5' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound
Pull sound updates from Takashi Iwai: "This is the first big chunk for 3.5 merges of sound stuff. There are a few big changes in different areas. First off, the streaming logic of USB-audio endpoints has been largely rewritten for the better support of "implicit feedback". If anything about USB got broken, this change has to be checked. For HD-audio, the resume procedure was changed; instead of delaying the resume of the hardware until the first use, now waking up immediately at resume. This is for buggy BIOS. For ASoC, dynamic PCM support and the improved support for digital links between off-SoC devices are major framework changes. Some highlights are below: * HD-audio - Avoid accesses of invalid pin-control bits that may stall the codec - V-ref setup cleanups - Fix the races in power-saving code - Fix the races in codec cache hashes and connection lists - Split some common codes for BIOS auto-parser to hda_auto_parser.c - Changed the PM resume code to wake up immediately for buggy BIOS - Creative SoundCore3D support - Add Conexant CX20751/2/3/4 codec support * ASoC - Dynamic PCM support, allowing support for SoCs with internal routing through components with tight sequencing and formatting constraints within their internal paths or where there are multiple components connected with CPU managed DMA controllers inside the SoC. - Greatly improved support for direct digital links between off-SoC devices, providing a much simpler way of connecting things like digital basebands to CODECs. - Much more fine grained and robust locking, cleaning up some of the confusion that crept in with multi-component. - CPU support for nVidia Tegra 30 I2S and audio hub controllers and ST-Ericsson MSP I2S controolers - New CODEC drivers for Cirrus CS42L52, LAPIS Semiconductor ML26124, Texas Instruments LM49453. - Some regmap changes needed by the Tegra I2S driver. - mc13783 audio support. * Misc - Rewrite with module_pci_driver() - Xonar DGX support for snd-oxygen - Improvement of packet handling in snd-firewire driver - New USB-endpoint streaming logic - Enhanced M-audio FTU quirks and relevant cleanups - Increment the support of OSS devices to 256 - snd-aloop accuracy improvement There are a few more pending changes for 3.5, but they will be sent slightly later as partly depending on the changes of DRM." Fix up conflicts in regmap (due to duplicate patches, with some further updates then having already come in from the regmap tree). Also some fairly trivial context conflicts in the imx and mcx soc drivers. * tag 'sound-3.5' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (280 commits) ALSA: snd-usb: fix stream info output in /proc ALSA: pcm - Add proper state checks to snd_pcm_drain() ALSA: sh: Fix up namespace collision in sh_dac_audio. ALSA: hda/realtek - Fix unused variable compile warning ASoC: sh: fsi: enable chip specific data transfer mode ASoC: sh: fsi: call fsi_hw_startup/shutdown from fsi_dai_trigger() ASoC: sh: fsi: use same format for IN/OUT ASoC: sh: fsi: add fsi_version() and removed meaningless version check ASoC: sh: fsi: use register field macro name on IN/OUT_DMAC ASoC: tegra: Add machine driver for WM8753 codec ALSA: hda - Fix possible races of accesses to connection list array ASoC: OMAP: HDMI: Introduce codec ARM: mx31_3ds: Add sound support ASoC: imx-mc13783 cleanup mx31moboard: Add sound support ASoC: mc13783 codec cleanups ASoC: add imx-mc13783 sound support ASoC: Add mc13783 codec mfd: mc13xxx: add codec platform data ASoC: don't flip master of DT-instantiated DAI links ...
Diffstat (limited to 'sound/soc/codecs/twl6040.c')
-rw-r--r--sound/soc/codecs/twl6040.c450
1 files changed, 15 insertions, 435 deletions
diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c
index dc7509b9d53a..a36e9fcdf184 100644
--- a/sound/soc/codecs/twl6040.c
+++ b/sound/soc/codecs/twl6040.c
@@ -46,17 +46,6 @@
#define TWL6040_OUTHF_0dB 0x03
#define TWL6040_OUTHF_M52dB 0x1D
-#define TWL6040_RAMP_NONE 0
-#define TWL6040_RAMP_UP 1
-#define TWL6040_RAMP_DOWN 2
-
-#define TWL6040_HSL_VOL_MASK 0x0F
-#define TWL6040_HSL_VOL_SHIFT 0
-#define TWL6040_HSR_VOL_MASK 0xF0
-#define TWL6040_HSR_VOL_SHIFT 4
-#define TWL6040_HF_VOL_MASK 0x1F
-#define TWL6040_HF_VOL_SHIFT 0
-
/* Shadow register used by the driver */
#define TWL6040_REG_SW_SHADOW 0x2F
#define TWL6040_CACHEREGNUM (TWL6040_REG_SW_SHADOW + 1)
@@ -64,18 +53,6 @@
/* TWL6040_REG_SW_SHADOW (0x2F) fields */
#define TWL6040_EAR_PATH_ENABLE 0x01
-struct twl6040_output {
- u16 active;
- u16 left_vol;
- u16 right_vol;
- u16 left_step;
- u16 right_step;
- unsigned int step_delay;
- u16 ramp;
- struct delayed_work work;
- struct completion ramp_done;
-};
-
struct twl6040_jack_data {
struct snd_soc_jack *jack;
struct delayed_work work;
@@ -100,8 +77,6 @@ struct twl6040_data {
struct snd_soc_codec *codec;
struct workqueue_struct *workqueue;
struct mutex mutex;
- struct twl6040_output headset;
- struct twl6040_output handsfree;
};
/*
@@ -311,318 +286,6 @@ static void twl6040_restore_regs(struct snd_soc_codec *codec)
}
}
-/*
- * Ramp HS PGA volume to minimise pops at stream startup and shutdown.
- */
-static inline int twl6040_hs_ramp_step(struct snd_soc_codec *codec,
- unsigned int left_step, unsigned int right_step)
-{
-
- struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
- struct twl6040_output *headset = &priv->headset;
- int left_complete = 0, right_complete = 0;
- u8 reg, val;
-
- /* left channel */
- left_step = (left_step > 0xF) ? 0xF : left_step;
- reg = twl6040_read_reg_cache(codec, TWL6040_REG_HSGAIN);
- val = (~reg & TWL6040_HSL_VOL_MASK);
-
- if (headset->ramp == TWL6040_RAMP_UP) {
- /* ramp step up */
- if (val < headset->left_vol) {
- if (val + left_step > headset->left_vol)
- val = headset->left_vol;
- else
- val += left_step;
-
- reg &= ~TWL6040_HSL_VOL_MASK;
- twl6040_write(codec, TWL6040_REG_HSGAIN,
- (reg | (~val & TWL6040_HSL_VOL_MASK)));
- } else {
- left_complete = 1;
- }
- } else if (headset->ramp == TWL6040_RAMP_DOWN) {
- /* ramp step down */
- if (val > 0x0) {
- if ((int)val - (int)left_step < 0)
- val = 0;
- else
- val -= left_step;
-
- reg &= ~TWL6040_HSL_VOL_MASK;
- twl6040_write(codec, TWL6040_REG_HSGAIN, reg |
- (~val & TWL6040_HSL_VOL_MASK));
- } else {
- left_complete = 1;
- }
- }
-
- /* right channel */
- right_step = (right_step > 0xF) ? 0xF : right_step;
- reg = twl6040_read_reg_cache(codec, TWL6040_REG_HSGAIN);
- val = (~reg & TWL6040_HSR_VOL_MASK) >> TWL6040_HSR_VOL_SHIFT;
-
- if (headset->ramp == TWL6040_RAMP_UP) {
- /* ramp step up */
- if (val < headset->right_vol) {
- if (val + right_step > headset->right_vol)
- val = headset->right_vol;
- else
- val += right_step;
-
- reg &= ~TWL6040_HSR_VOL_MASK;
- twl6040_write(codec, TWL6040_REG_HSGAIN,
- (reg | (~val << TWL6040_HSR_VOL_SHIFT)));
- } else {
- right_complete = 1;
- }
- } else if (headset->ramp == TWL6040_RAMP_DOWN) {
- /* ramp step down */
- if (val > 0x0) {
- if ((int)val - (int)right_step < 0)
- val = 0;
- else
- val -= right_step;
-
- reg &= ~TWL6040_HSR_VOL_MASK;
- twl6040_write(codec, TWL6040_REG_HSGAIN,
- reg | (~val << TWL6040_HSR_VOL_SHIFT));
- } else {
- right_complete = 1;
- }
- }
-
- return left_complete & right_complete;
-}
-
-/*
- * Ramp HF PGA volume to minimise pops at stream startup and shutdown.
- */
-static inline int twl6040_hf_ramp_step(struct snd_soc_codec *codec,
- unsigned int left_step, unsigned int right_step)
-{
- struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
- struct twl6040_output *handsfree = &priv->handsfree;
- int left_complete = 0, right_complete = 0;
- u16 reg, val;
-
- /* left channel */
- left_step = (left_step > 0x1D) ? 0x1D : left_step;
- reg = twl6040_read_reg_cache(codec, TWL6040_REG_HFLGAIN);
- reg = 0x1D - reg;
- val = (reg & TWL6040_HF_VOL_MASK);
- if (handsfree->ramp == TWL6040_RAMP_UP) {
- /* ramp step up */
- if (val < handsfree->left_vol) {
- if (val + left_step > handsfree->left_vol)
- val = handsfree->left_vol;
- else
- val += left_step;
-
- reg &= ~TWL6040_HF_VOL_MASK;
- twl6040_write(codec, TWL6040_REG_HFLGAIN,
- reg | (0x1D - val));
- } else {
- left_complete = 1;
- }
- } else if (handsfree->ramp == TWL6040_RAMP_DOWN) {
- /* ramp step down */
- if (val > 0) {
- if ((int)val - (int)left_step < 0)
- val = 0;
- else
- val -= left_step;
-
- reg &= ~TWL6040_HF_VOL_MASK;
- twl6040_write(codec, TWL6040_REG_HFLGAIN,
- reg | (0x1D - val));
- } else {
- left_complete = 1;
- }
- }
-
- /* right channel */
- right_step = (right_step > 0x1D) ? 0x1D : right_step;
- reg = twl6040_read_reg_cache(codec, TWL6040_REG_HFRGAIN);
- reg = 0x1D - reg;
- val = (reg & TWL6040_HF_VOL_MASK);
- if (handsfree->ramp == TWL6040_RAMP_UP) {
- /* ramp step up */
- if (val < handsfree->right_vol) {
- if (val + right_step > handsfree->right_vol)
- val = handsfree->right_vol;
- else
- val += right_step;
-
- reg &= ~TWL6040_HF_VOL_MASK;
- twl6040_write(codec, TWL6040_REG_HFRGAIN,
- reg | (0x1D - val));
- } else {
- right_complete = 1;
- }
- } else if (handsfree->ramp == TWL6040_RAMP_DOWN) {
- /* ramp step down */
- if (val > 0) {
- if ((int)val - (int)right_step < 0)
- val = 0;
- else
- val -= right_step;
-
- reg &= ~TWL6040_HF_VOL_MASK;
- twl6040_write(codec, TWL6040_REG_HFRGAIN,
- reg | (0x1D - val));
- }
- }
-
- return left_complete & right_complete;
-}
-
-/*
- * This work ramps both output PGAs at stream start/stop time to
- * minimise pop associated with DAPM power switching.
- */
-static void twl6040_pga_hs_work(struct work_struct *work)
-{
- struct twl6040_data *priv =
- container_of(work, struct twl6040_data, headset.work.work);
- struct snd_soc_codec *codec = priv->codec;
- struct twl6040_output *headset = &priv->headset;
- int i, headset_complete;
-
- /* do we need to ramp at all ? */
- if (headset->ramp == TWL6040_RAMP_NONE)
- return;
-
- /* HS PGA gain range: 0x0 - 0xf (0 - 15) */
- for (i = 0; i < 16; i++) {
- headset_complete = twl6040_hs_ramp_step(codec,
- headset->left_step,
- headset->right_step);
-
- /* ramp finished ? */
- if (headset_complete)
- break;
-
- schedule_timeout_interruptible(
- msecs_to_jiffies(headset->step_delay));
- }
-
- if (headset->ramp == TWL6040_RAMP_DOWN) {
- headset->active = 0;
- complete(&headset->ramp_done);
- } else {
- headset->active = 1;
- }
- headset->ramp = TWL6040_RAMP_NONE;
-}
-
-static void twl6040_pga_hf_work(struct work_struct *work)
-{
- struct twl6040_data *priv =
- container_of(work, struct twl6040_data, handsfree.work.work);
- struct snd_soc_codec *codec = priv->codec;
- struct twl6040_output *handsfree = &priv->handsfree;
- int i, handsfree_complete;
-
- /* do we need to ramp at all ? */
- if (handsfree->ramp == TWL6040_RAMP_NONE)
- return;
-
- /*
- * HF PGA gain range: 0x00 - 0x1d (0 - 29) */
- for (i = 0; i < 30; i++) {
- handsfree_complete = twl6040_hf_ramp_step(codec,
- handsfree->left_step,
- handsfree->right_step);
-
- /* ramp finished ? */
- if (handsfree_complete)
- break;
-
- schedule_timeout_interruptible(
- msecs_to_jiffies(handsfree->step_delay));
- }
-
-
- if (handsfree->ramp == TWL6040_RAMP_DOWN) {
- handsfree->active = 0;
- complete(&handsfree->ramp_done);
- } else
- handsfree->active = 1;
- handsfree->ramp = TWL6040_RAMP_NONE;
-}
-
-static int out_drv_event(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *kcontrol, int event)
-{
- struct snd_soc_codec *codec = w->codec;
- struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
- struct twl6040_output *out;
- struct delayed_work *work;
-
- switch (w->shift) {
- case 2: /* Headset output driver */
- out = &priv->headset;
- work = &out->work;
- /*
- * Make sure, that we do not mess up variables for already
- * executing work.
- */
- cancel_delayed_work_sync(work);
-
- out->left_step = priv->hs_left_step;
- out->right_step = priv->hs_right_step;
- out->step_delay = 5; /* 5 ms between volume ramp steps */
- break;
- case 4: /* Handsfree output driver */
- out = &priv->handsfree;
- work = &out->work;
- /*
- * Make sure, that we do not mess up variables for already
- * executing work.
- */
- cancel_delayed_work_sync(work);
-
- out->left_step = priv->hf_left_step;
- out->right_step = priv->hf_right_step;
- out->step_delay = 5; /* 5 ms between volume ramp steps */
- break;
- default:
- return -1;
- }
-
- switch (event) {
- case SND_SOC_DAPM_POST_PMU:
- if (out->active)
- break;
-
- /* don't use volume ramp for power-up */
- out->ramp = TWL6040_RAMP_UP;
- out->left_step = out->left_vol;
- out->right_step = out->right_vol;
-
- queue_delayed_work(priv->workqueue, work, msecs_to_jiffies(1));
- break;
-
- case SND_SOC_DAPM_PRE_PMD:
- if (!out->active)
- break;
-
- /* use volume ramp for power-down */
- out->ramp = TWL6040_RAMP_DOWN;
- INIT_COMPLETION(out->ramp_done);
-
- queue_delayed_work(priv->workqueue, work, msecs_to_jiffies(1));
-
- wait_for_completion_timeout(&out->ramp_done,
- msecs_to_jiffies(2000));
- break;
- }
-
- return 0;
-}
-
/* set headset dac and driver power mode */
static int headset_power_mode(struct snd_soc_codec *codec, int high_perf)
{
@@ -747,71 +410,6 @@ static irqreturn_t twl6040_audio_handler(int irq, void *data)
return IRQ_HANDLED;
}
-static int twl6040_put_volsw(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec);
- struct twl6040_output *out = NULL;
- struct soc_mixer_control *mc =
- (struct soc_mixer_control *)kcontrol->private_value;
- int ret;
-
- /* For HS and HF we shadow the values and only actually write
- * them out when active in order to ensure the amplifier comes on
- * as quietly as possible. */
- switch (mc->reg) {
- case TWL6040_REG_HSGAIN:
- out = &twl6040_priv->headset;
- break;
- case TWL6040_REG_HFLGAIN:
- out = &twl6040_priv->handsfree;
- break;
- default:
- dev_warn(codec->dev, "%s: Unexpected register: 0x%02x\n",
- __func__, mc->reg);
- return -EINVAL;
- }
-
- out->left_vol = ucontrol->value.integer.value[0];
- out->right_vol = ucontrol->value.integer.value[1];
- if (!out->active)
- return 1;
-
- ret = snd_soc_put_volsw(kcontrol, ucontrol);
- if (ret < 0)
- return ret;
-
- return 1;
-}
-
-static int twl6040_get_volsw(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec);
- struct twl6040_output *out = &twl6040_priv->headset;
- struct soc_mixer_control *mc =
- (struct soc_mixer_control *)kcontrol->private_value;
-
- switch (mc->reg) {
- case TWL6040_REG_HSGAIN:
- out = &twl6040_priv->headset;
- break;
- case TWL6040_REG_HFLGAIN:
- out = &twl6040_priv->handsfree;
- break;
- default:
- dev_warn(codec->dev, "%s: Unexpected register: 0x%02x\n",
- __func__, mc->reg);
- return -EINVAL;
- }
-
- ucontrol->value.integer.value[0] = out->left_vol;
- ucontrol->value.integer.value[1] = out->right_vol;
- return 0;
-}
-
static int twl6040_soc_dapm_put_vibra_enum(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
@@ -1076,12 +674,10 @@ static const struct snd_kcontrol_new twl6040_snd_controls[] = {
TWL6040_REG_LINEGAIN, 0, 3, 7, 0, afm_amp_tlv),
/* Playback gains */
- SOC_DOUBLE_EXT_TLV("Headset Playback Volume",
- TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, twl6040_get_volsw,
- twl6040_put_volsw, hs_tlv),
- SOC_DOUBLE_R_EXT_TLV("Handsfree Playback Volume",
- TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1,
- twl6040_get_volsw, twl6040_put_volsw, hf_tlv),
+ SOC_DOUBLE_TLV("Headset Playback Volume",
+ TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, hs_tlv),
+ SOC_DOUBLE_R_TLV("Handsfree Playback Volume",
+ TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, hf_tlv),
SOC_SINGLE_TLV("Earphone Playback Volume",
TWL6040_REG_EARCTL, 1, 0xF, 1, ep_tlv),
@@ -1180,22 +776,14 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = {
&auxr_switch_control),
/* Analog playback drivers */
- SND_SOC_DAPM_OUT_DRV_E("HF Left Driver",
- TWL6040_REG_HFLCTL, 4, 0, NULL, 0,
- out_drv_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
- SND_SOC_DAPM_OUT_DRV_E("HF Right Driver",
- TWL6040_REG_HFRCTL, 4, 0, NULL, 0,
- out_drv_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
- SND_SOC_DAPM_OUT_DRV_E("HS Left Driver",
- TWL6040_REG_HSLCTL, 2, 0, NULL, 0,
- out_drv_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
- SND_SOC_DAPM_OUT_DRV_E("HS Right Driver",
- TWL6040_REG_HSRCTL, 2, 0, NULL, 0,
- out_drv_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_OUT_DRV("HF Left Driver",
+ TWL6040_REG_HFLCTL, 4, 0, NULL, 0),
+ SND_SOC_DAPM_OUT_DRV("HF Right Driver",
+ TWL6040_REG_HFRCTL, 4, 0, NULL, 0),
+ SND_SOC_DAPM_OUT_DRV("HS Left Driver",
+ TWL6040_REG_HSLCTL, 2, 0, NULL, 0),
+ SND_SOC_DAPM_OUT_DRV("HS Right Driver",
+ TWL6040_REG_HSRCTL, 2, 0, NULL, 0),
SND_SOC_DAPM_OUT_DRV_E("Earphone Driver",
TWL6040_REG_EARCTL, 0, 0, NULL, 0,
twl6040_ep_drv_event,
@@ -1339,8 +927,7 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec,
static int twl6040_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_codec *codec = dai->codec;
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
snd_pcm_hw_constraint_list(substream->runtime, 0,
@@ -1354,8 +941,7 @@ static int twl6040_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_codec *codec = dai->codec;
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
int rate;
@@ -1391,8 +977,7 @@ static int twl6040_hw_params(struct snd_pcm_substream *substream,
static int twl6040_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_codec *codec = dai->codec;
struct twl6040 *twl6040 = codec->control_data;
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
int ret;
@@ -1570,14 +1155,9 @@ static int twl6040_probe(struct snd_soc_codec *codec)
}
INIT_DELAYED_WORK(&priv->hs_jack.work, twl6040_accessory_work);
- INIT_DELAYED_WORK(&priv->headset.work, twl6040_pga_hs_work);
- INIT_DELAYED_WORK(&priv->handsfree.work, twl6040_pga_hf_work);
mutex_init(&priv->mutex);
- init_completion(&priv->headset.ramp_done);
- init_completion(&priv->handsfree.ramp_done);
-
ret = request_threaded_irq(priv->plug_irq, NULL, twl6040_audio_handler,
0, "twl6040_irq_plug", codec);
if (ret) {