diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-05-23 13:05:43 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-05-23 13:05:43 -0700 |
commit | 2e341ca686042aa464efa755447e7bcee91d1eb6 (patch) | |
tree | c6b16b6b6a6e871fa04396cb2c7eb759bcad5be3 /sound/soc/codecs/twl6040.c | |
parent | 927ad551031798d4cba49766549600bbb33872d7 (diff) | |
parent | 85e184e4c3cd3e2285ceab91ff8f0cac094e8a85 (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.c | 450 |
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) { |