/* ** ============================================================================= ** Copyright (c) 2016 Texas Instruments Inc. ** ** This program is free software; you can redistribute it and/or modify it under ** the terms of the GNU General Public License as published by the Free Software ** Foundation; version 2. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ** FOR A PARTICULAR PURPOSE.See the GNU General Public License for more details. ** ** File: ** tas2770-codec.c ** ** Description: ** ALSA SoC driver for TAS2770 20-W Digital Input Mono Class-D Audio Amplifier ** with Speaker I/V Sense ** ** ============================================================================= */ #ifdef CONFIG_TAS2770_CODEC #define DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tas2770.h" #define TAS2770_MDELAY 0xFFFFFFFE static int tas2770_set_slot(struct snd_soc_codec *codec, int slot_width); static unsigned int tas2770_codec_read(struct snd_soc_codec *codec, unsigned int reg) { struct tas2770_priv *pTAS2770 = snd_soc_codec_get_drvdata(codec); int nResult = 0; unsigned int value = 0; mutex_lock(&pTAS2770->dev_lock); nResult = regmap_read(pTAS2770->regmap, reg, &value); if (nResult < 0) dev_err(pTAS2770->dev, "%s, ERROR, reg=0x%x, E=%d\n", __func__, reg, nResult); else dev_dbg(pTAS2770->dev, "%s, reg: 0x%x, value: 0x%x\n", __func__, reg, value); mutex_unlock(&pTAS2770->dev_lock); if (nResult >= 0) return value; else return nResult; } static int tas2770_codec_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { struct tas2770_priv *pTAS2770 = snd_soc_codec_get_drvdata(codec); int nResult = 0; mutex_lock(&pTAS2770->dev_lock); nResult = regmap_write(pTAS2770->regmap, reg, value); if (nResult < 0) dev_err(pTAS2770->dev, "%s, ERROR, reg=0x%x, E=%d\n", __func__, reg, nResult); else dev_dbg(pTAS2770->dev, "%s, reg: 0x%x, 0x%x\n", __func__, reg, value); mutex_unlock(&pTAS2770->dev_lock); return nResult; } static int tas2770_codec_suspend(struct snd_soc_codec *codec) { struct tas2770_priv *pTAS2770 = snd_soc_codec_get_drvdata(codec); int ret = 0; mutex_lock(&pTAS2770->codec_lock); dev_dbg(pTAS2770->dev, "%s\n", __func__); pTAS2770->runtime_suspend(pTAS2770); mutex_unlock(&pTAS2770->codec_lock); return ret; } static int tas2770_codec_resume(struct snd_soc_codec *codec) { struct tas2770_priv *pTAS2770 = snd_soc_codec_get_drvdata(codec); int ret = 0; mutex_lock(&pTAS2770->codec_lock); dev_dbg(pTAS2770->dev, "%s\n", __func__); pTAS2770->runtime_resume(pTAS2770); mutex_unlock(&pTAS2770->codec_lock); return ret; } static const char * const tas2770_ASI1_src[] = { "I2C offset", "Left", "Right", "LeftRightDiv2", }; static SOC_ENUM_SINGLE_DECL( tas2770_ASI1_src_enum, TAS2770_TDMConfigurationReg2, 4, tas2770_ASI1_src); static const struct snd_kcontrol_new tas2770_asi1_mux = SOC_DAPM_ENUM("ASI1 Source", tas2770_ASI1_src_enum); static int tas2770_set_power_state(struct tas2770_priv *pTAS2770, int state) { struct snd_soc_codec *codec = pTAS2770->codec; switch (state) { case TAS2770_POWER_ACTIVE: snd_soc_update_bits(codec, TAS2770_PowerControl, TAS2770_PowerControl_OperationalMode10_Mask, TAS2770_PowerControl_OperationalMode10_Active); pTAS2770->mnPowerState = TAS2770_POWER_ACTIVE; pTAS2770->enableIRQ(pTAS2770, true); break; case TAS2770_POWER_MUTE: snd_soc_update_bits(codec, TAS2770_PowerControl, TAS2770_PowerControl_OperationalMode10_Mask, TAS2770_PowerControl_OperationalMode10_Mute); pTAS2770->mnPowerState = TAS2770_POWER_MUTE; break; case TAS2770_POWER_SHUTDOWN: snd_soc_update_bits(codec, TAS2770_PowerControl, TAS2770_PowerControl_OperationalMode10_Mask, TAS2770_PowerControl_OperationalMode10_Shutdown); pTAS2770->enableIRQ(pTAS2770, false); pTAS2770->mnPowerState = TAS2770_POWER_SHUTDOWN; break; default: dev_err(pTAS2770->dev, "wrong power state setting %d\n", state); } pTAS2770->mnPowerState = state; return 0; } static int tas2770_dac_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct tas2770_priv *pTAS2770 = snd_soc_codec_get_drvdata(codec); mutex_lock(&pTAS2770->codec_lock); switch (event) { case SND_SOC_DAPM_POST_PMU: tas2770_set_power_state(pTAS2770, TAS2770_POWER_MUTE); break; case SND_SOC_DAPM_PRE_PMD: tas2770_set_power_state(pTAS2770, TAS2770_POWER_SHUTDOWN); break; } mutex_unlock(&pTAS2770->codec_lock); return 0; } static const struct snd_kcontrol_new isense_switch = SOC_DAPM_SINGLE("Switch", TAS2770_PowerControl, 3, 1, 1); static const struct snd_kcontrol_new vsense_switch = SOC_DAPM_SINGLE("Switch", TAS2770_PowerControl, 2, 1, 1); static const struct snd_soc_dapm_widget tas2770_dapm_widgets[] = { SND_SOC_DAPM_AIF_IN("ASI1", "ASI1 Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_MUX("ASI1 Sel", SND_SOC_NOPM, 0, 0, &tas2770_asi1_mux), SND_SOC_DAPM_SWITCH("ISENSE", TAS2770_PowerControl, 3, 1, &isense_switch), SND_SOC_DAPM_SWITCH("VSENSE", TAS2770_PowerControl, 2, 1, &vsense_switch), SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas2770_dac_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_OUTPUT("OUT"), SND_SOC_DAPM_SIGGEN("VMON"), SND_SOC_DAPM_SIGGEN("IMON") }; static const struct snd_soc_dapm_route tas2770_audio_map[] = { {"ASI1 Sel", "I2C offset", "ASI1"}, {"ASI1 Sel", "Left", "ASI1"}, {"ASI1 Sel", "Right", "ASI1"}, {"ASI1 Sel", "LeftRightDiv2", "ASI1"}, {"DAC", NULL, "ASI1 Sel"}, {"OUT", NULL, "DAC"}, {"ISENSE", "Switch", "IMON"}, {"VSENSE", "Switch", "VMON"}, }; static int tas2770_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; struct tas2770_priv *pTAS2770 = snd_soc_codec_get_drvdata(codec); dev_dbg(pTAS2770->dev, "%s\n", __func__); mutex_lock(&pTAS2770->codec_lock); if (mute) { tas2770_set_power_state(pTAS2770, TAS2770_POWER_MUTE); } else { tas2770_set_power_state(pTAS2770, TAS2770_POWER_ACTIVE); } mutex_unlock(&pTAS2770->codec_lock); return 0; } static int tas2770_set_bitwidth(struct tas2770_priv *pTAS2770, int bitwidth) { switch (bitwidth) { case SNDRV_PCM_FORMAT_S16_LE: snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg2, TAS2770_TDMConfigurationReg2_RXWLEN32_Mask, TAS2770_TDMConfigurationReg2_RXWLEN32_16Bits); /* If machine driver did not call set slot width */ if (pTAS2770->mnSlot_width == 0) tas2770_set_slot(pTAS2770->codec, 16); pTAS2770->mnVmon_slot_no = pTAS2770->mnImon_slot_no + 2; break; case SNDRV_PCM_FORMAT_S24_LE: snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg2, TAS2770_TDMConfigurationReg2_RXWLEN32_Mask, TAS2770_TDMConfigurationReg2_RXWLEN32_24Bits); if (pTAS2770->mnSlot_width == 0) tas2770_set_slot(pTAS2770->codec, 32); pTAS2770->mnVmon_slot_no = pTAS2770->mnImon_slot_no + 4; break; case SNDRV_PCM_FORMAT_S32_LE: snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg2, TAS2770_TDMConfigurationReg2_RXWLEN32_Mask, TAS2770_TDMConfigurationReg2_RXWLEN32_32Bits); if (pTAS2770->mnSlot_width == 0) tas2770_set_slot(pTAS2770->codec, 32); pTAS2770->mnVmon_slot_no = pTAS2770->mnImon_slot_no + 4; break; default: dev_dbg(pTAS2770->dev, "Not supported params format\n"); return -EINVAL; } pTAS2770->mnCh_size = bitwidth; dev_dbg(pTAS2770->dev, "mnCh_size: %d\n", pTAS2770->mnCh_size); snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg5, TAS2770_TDMConfigurationReg5_VSNSTX_Mask | TAS2770_TDMConfigurationReg5_VSNSSLOT50_Mask, TAS2770_TDMConfigurationReg5_VSNSTX_Enable | pTAS2770->mnVmon_slot_no); snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg6, TAS2770_TDMConfigurationReg6_ISNSTX_Mask | TAS2770_TDMConfigurationReg6_ISNSSLOT50_Mask, TAS2770_TDMConfigurationReg6_ISNSTX_Enable | pTAS2770->mnImon_slot_no); return 0; } static int tas2770_set_samplerate(struct tas2770_priv *pTAS2770, int samplerate) { switch (samplerate) { case 48000: snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg0, TAS2770_TDMConfigurationReg0_SAMPRATERAMP_Mask, TAS2770_TDMConfigurationReg0_SAMPRATERAMP_48KHz); snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg0, TAS2770_TDMConfigurationReg0_SAMPRATE31_Mask, TAS2770_TDMConfigurationReg0_SAMPRATE31_44_1_48kHz); break; case 44100: snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg0, TAS2770_TDMConfigurationReg0_SAMPRATERAMP_Mask, TAS2770_TDMConfigurationReg0_SAMPRATERAMP_44_1KHz); snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg0, TAS2770_TDMConfigurationReg0_SAMPRATE31_Mask, TAS2770_TDMConfigurationReg0_SAMPRATE31_44_1_48kHz); break; case 96000: snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg0, TAS2770_TDMConfigurationReg0_SAMPRATERAMP_Mask, TAS2770_TDMConfigurationReg0_SAMPRATERAMP_48KHz); snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg0, TAS2770_TDMConfigurationReg0_SAMPRATE31_Mask, TAS2770_TDMConfigurationReg0_SAMPRATE31_88_2_96kHz); break; case 88200: snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg0, TAS2770_TDMConfigurationReg0_SAMPRATERAMP_Mask, TAS2770_TDMConfigurationReg0_SAMPRATERAMP_44_1KHz); snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg0, TAS2770_TDMConfigurationReg0_SAMPRATE31_Mask, TAS2770_TDMConfigurationReg0_SAMPRATE31_88_2_96kHz); break; case 19200: snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg0, TAS2770_TDMConfigurationReg0_SAMPRATERAMP_Mask, TAS2770_TDMConfigurationReg0_SAMPRATERAMP_48KHz); snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg0, TAS2770_TDMConfigurationReg0_SAMPRATE31_Mask, TAS2770_TDMConfigurationReg0_SAMPRATE31_176_4_192kHz); break; case 17640: snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg0, TAS2770_TDMConfigurationReg0_SAMPRATERAMP_Mask, TAS2770_TDMConfigurationReg0_SAMPRATERAMP_44_1KHz); snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg0, TAS2770_TDMConfigurationReg0_SAMPRATE31_Mask, TAS2770_TDMConfigurationReg0_SAMPRATE31_176_4_192kHz); break; default: dev_dbg(pTAS2770->dev, "%s, unsupported sample rate\n", __func__); } pTAS2770->mnSamplingRate = samplerate; return 0; } static int tas2770_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; struct tas2770_priv *pTAS2770 = snd_soc_codec_get_drvdata(codec); int ret = 0; dev_dbg(pTAS2770->dev, "%s, format: %d\n", __func__, params_format(params)); mutex_lock(&pTAS2770->codec_lock); ret = tas2770_set_bitwidth(pTAS2770, params_format(params)); if(ret < 0) return ret; dev_dbg(pTAS2770->dev, "%s, sample rate: %d\n", __func__, params_rate(params)); ret = tas2770_set_samplerate(pTAS2770, params_rate(params)); mutex_unlock(&pTAS2770->codec_lock); return ret; } static int tas2770_set_fmt(struct tas2770_priv *pTAS2770, unsigned int fmt) { u8 tdm_rx_start_slot = 0, asi_cfg_1 = 0; int ret = 0; int value = 0; switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: asi_cfg_1 = 0x00; break; default: dev_err(pTAS2770->dev, "ASI format master is not found\n"); ret = -EINVAL; } switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: dev_dbg(pTAS2770->dev, "INV format: NBNF\n"); asi_cfg_1 |= TAS2770_TDMConfigurationReg1_RXEDGE_Rising; break; case SND_SOC_DAIFMT_IB_NF: dev_dbg(pTAS2770->dev, "INV format: IBNF\n"); asi_cfg_1 |= TAS2770_TDMConfigurationReg1_RXEDGE_Falling; break; default: dev_err(pTAS2770->dev, "ASI format Inverse is not found\n"); ret = -EINVAL; } snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg1, TAS2770_TDMConfigurationReg1_RXEDGE_Mask, asi_cfg_1); switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case (SND_SOC_DAIFMT_I2S): tdm_rx_start_slot = 1; break; case (SND_SOC_DAIFMT_DSP_A): case (SND_SOC_DAIFMT_DSP_B): tdm_rx_start_slot = 1; break; case (SND_SOC_DAIFMT_LEFT_J): tdm_rx_start_slot = 0; break; default: dev_err(pTAS2770->dev, "DAI Format is not found, fmt=0x%x\n", fmt); ret = -EINVAL; break; } snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg1, TAS2770_TDMConfigurationReg1_RXOFFSET51_Mask, (tdm_rx_start_slot << TAS2770_TDMConfigurationReg1_RXOFFSET51_Shift)); snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg3, TAS2770_TDMConfigurationReg3_RXSLOTLeft30_Mask, (pTAS2770->mnLeftSlot << TAS2770_TDMConfigurationReg3_RXSLOTLeft30_Shift)); snd_soc_update_bits(pTAS2770->codec, TAS2770_TDMConfigurationReg3, TAS2770_TDMConfigurationReg3_RXSLOTRight74_Mask, (pTAS2770->mnRightSlot << TAS2770_TDMConfigurationReg3_RXSLOTRight74_Shift)); value = snd_soc_read(pTAS2770->codec, TAS2770_TDMConfigurationReg3); dev_dbg(pTAS2770->dev, "slot value: 0x%x", value); pTAS2770->mnASIFormat = fmt; return 0; } static int tas2770_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_codec *codec = dai->codec; struct tas2770_priv *pTAS2770 = snd_soc_codec_get_drvdata(codec); int ret = 0; dev_dbg(pTAS2770->dev, "%s, format=0x%x\n", __func__, fmt); mutex_lock(&pTAS2770->codec_lock); ret = tas2770_set_fmt(pTAS2770, fmt); mutex_unlock(&pTAS2770->codec_lock); return ret; } static int tas2770_set_slot(struct snd_soc_codec *codec, int slot_width) { int ret = 0; struct tas2770_priv *pTAS2770 = snd_soc_codec_get_drvdata(codec); switch (slot_width) { case 16: ret = snd_soc_update_bits(codec, TAS2770_TDMConfigurationReg2, TAS2770_TDMConfigurationReg2_RXSLEN10_Mask, TAS2770_TDMConfigurationReg2_RXSLEN10_16Bits); break; case 24: ret = snd_soc_update_bits(codec, TAS2770_TDMConfigurationReg2, TAS2770_TDMConfigurationReg2_RXSLEN10_Mask, TAS2770_TDMConfigurationReg2_RXSLEN10_24Bits); break; case 32: ret = snd_soc_update_bits(codec, TAS2770_TDMConfigurationReg2, TAS2770_TDMConfigurationReg2_RXSLEN10_Mask, TAS2770_TDMConfigurationReg2_RXSLEN10_32Bits); break; case 0: /* Do not change slot width */ break; default: dev_dbg(pTAS2770->dev, "slot width not supported"); ret = -EINVAL; } if (ret >= 0) pTAS2770->mnSlot_width = slot_width; return ret; } static int tas2770_set_dai_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { int ret = 0; struct snd_soc_codec *codec = dai->codec; struct tas2770_priv *pTAS2770 = snd_soc_codec_get_drvdata(codec); dev_dbg(pTAS2770->dev, "%s, tx_mask:%d, rx_mask:%d, slots:%d, slot_width:%d", __func__, tx_mask, rx_mask, slots, slot_width); mutex_lock(&pTAS2770->codec_lock); ret = tas2770_set_slot(codec, slot_width); mutex_unlock(&pTAS2770->codec_lock); return ret; } static struct snd_soc_dai_ops tas2770_dai_ops = { .digital_mute = tas2770_mute, .hw_params = tas2770_hw_params, .set_fmt = tas2770_set_dai_fmt, .set_tdm_slot = tas2770_set_dai_tdm_slot, }; #define TAS2770_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) #define TAS2770_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ SNDRV_PCM_RATE_96000 |\ SNDRV_PCM_RATE_192000\ ) static struct snd_soc_dai_driver tas2770_dai_driver[] = { { .name = "tas2770 ASI1", .id = 0, .playback = { .stream_name = "ASI1 Playback", .channels_min = 2, .channels_max = 2, .rates = TAS2770_RATES, .formats = TAS2770_FORMATS, }, .capture = { .stream_name = "ASI1 Capture", .channels_min = 0, .channels_max = 2, .rates = TAS2770_RATES, .formats = TAS2770_FORMATS, }, .ops = &tas2770_dai_ops, .symmetric_rates = 1, }, }; static int tas2770_codec_probe(struct snd_soc_codec *codec) { struct tas2770_priv *pTAS2770 = snd_soc_codec_get_drvdata(codec); dev_err(pTAS2770->dev, "%s\n", __func__); pTAS2770->codec = codec; return 0; } static int tas2770_codec_remove(struct snd_soc_codec *codec) { return 0; } static DECLARE_TLV_DB_SCALE(tas2770_digital_tlv, 1100, 50, 0); static DECLARE_TLV_DB_SCALE(tas2770_playback_volume, -12750, 50, 0); static const struct snd_kcontrol_new tas2770_snd_controls[] = { SOC_SINGLE_TLV("Amp Output Level", TAS2770_PlaybackConfigurationReg0, 0, 0x14, 0, tas2770_digital_tlv), SOC_SINGLE_TLV("Playback Volume", TAS2770_PlaybackConfigurationReg2, 0, TAS2770_PlaybackConfigurationReg2_VOLMAX, 1, tas2770_playback_volume), }; static struct snd_soc_codec_driver soc_codec_driver_tas2770 = { .probe = tas2770_codec_probe, .remove = tas2770_codec_remove, .read = tas2770_codec_read, .write = tas2770_codec_write, .suspend = tas2770_codec_suspend, .resume = tas2770_codec_resume, .component_driver = { .controls = tas2770_snd_controls, .num_controls = ARRAY_SIZE(tas2770_snd_controls), .dapm_widgets = tas2770_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(tas2770_dapm_widgets), .dapm_routes = tas2770_audio_map, .num_dapm_routes = ARRAY_SIZE(tas2770_audio_map), }, }; int tas2770_register_codec(struct tas2770_priv *pTAS2770) { int nResult = 0; dev_info(pTAS2770->dev, "%s, enter\n", __func__); nResult = snd_soc_register_codec(pTAS2770->dev, &soc_codec_driver_tas2770, tas2770_dai_driver, ARRAY_SIZE(tas2770_dai_driver)); return nResult; } int tas2770_deregister_codec(struct tas2770_priv *pTAS2770) { snd_soc_unregister_codec(pTAS2770->dev); return 0; } void tas2770_LoadConfig(struct tas2770_priv *pTAS2770) { int ret = 0; pTAS2770->hw_reset(pTAS2770); snd_soc_write(pTAS2770->codec, TAS2770_SoftwareReset, TAS2770_SoftwareReset_SoftwareReset_Reset); ret = tas2770_set_slot(pTAS2770->codec, pTAS2770->mnSlot_width); if (ret < 0) goto end; ret = tas2770_set_fmt(pTAS2770, pTAS2770->mnASIFormat); if (ret < 0) goto end; ret = tas2770_set_bitwidth(pTAS2770, pTAS2770->mnCh_size); if (ret < 0) goto end; ret = tas2770_set_samplerate(pTAS2770, pTAS2770->mnSamplingRate); if (ret < 0) goto end; ret = tas2770_set_power_state(pTAS2770, pTAS2770->mnPowerState); end: /* power up failed, restart later */ if (ret < 0) schedule_delayed_work(&pTAS2770->irq_work, msecs_to_jiffies(1000)); } MODULE_AUTHOR("Texas Instruments Inc."); MODULE_DESCRIPTION("TAS2770 ALSA SOC Smart Amplifier driver"); MODULE_LICENSE("GPL v2"); #endif /* CONFIG_TAS2770_CODEC */