summaryrefslogtreecommitdiff
path: root/sound/soc/mxs
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/mxs')
-rw-r--r--sound/soc/mxs/mxs-adc.c246
-rw-r--r--sound/soc/mxs/mxs-dai.c1
-rw-r--r--sound/soc/mxs/mxs-evk-adc.c107
-rw-r--r--sound/soc/mxs/mxs-pcm.c4
4 files changed, 311 insertions, 47 deletions
diff --git a/sound/soc/mxs/mxs-adc.c b/sound/soc/mxs/mxs-adc.c
index e8bb4255fff5..7069927b1ac3 100644
--- a/sound/soc/mxs/mxs-adc.c
+++ b/sound/soc/mxs/mxs-adc.c
@@ -37,6 +37,7 @@
#define MXS_ADC_RATES SNDRV_PCM_RATE_8000_192000
#define MXS_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
+#define ADC_VOLUME_MIN 0x37
struct mxs_pcm_dma_params mxs_audio_in = {
.name = "mxs-audio-in",
@@ -50,6 +51,166 @@ struct mxs_pcm_dma_params mxs_audio_out = {
.irq = IRQ_DAC_DMA,
};
+static struct delayed_work work;
+static struct delayed_work adc_ramp_work;
+static struct delayed_work dac_ramp_work;
+static bool adc_ramp_done = 1;
+static bool dac_ramp_done = 1;
+
+static void mxs_adc_schedule_work(struct delayed_work *work)
+{
+ schedule_delayed_work(work, HZ / 10);
+}
+static void mxs_adc_work(struct work_struct *work)
+{
+ /* disable irq */
+ disable_irq(IRQ_HEADPHONE_SHORT);
+
+ while (true) {
+ __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR);
+ msleep(10);
+ if ((__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL)
+ & BM_AUDIOOUT_ANACTRL_SHORT_LR_STS) != 0) {
+ /* rearm the short protection */
+ __raw_writel(BM_AUDIOOUT_ANACTRL_SHORTMODE_LR,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_LR_STS,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ __raw_writel(BF_AUDIOOUT_ANACTRL_SHORTMODE_LR(0x1),
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+
+ __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+ printk(KERN_WARNING "WARNING : Headphone LR short!\r\n");
+ } else {
+ printk(KERN_WARNING "INFO : Headphone LR no longer short!\r\n");
+ break;
+ }
+ msleep(1000);
+ }
+
+ /* power up the HEADPHONE and un-mute the HPVOL */
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_CLR);
+ __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR);
+
+ /* enable irq for next short detect*/
+ enable_irq(IRQ_HEADPHONE_SHORT);
+}
+
+static void mxs_adc_schedule_ramp_work(struct delayed_work *work)
+{
+ schedule_delayed_work(work, msecs_to_jiffies(2));
+ adc_ramp_done = 0;
+}
+
+static void mxs_adc_ramp_work(struct work_struct *work)
+{
+ u32 reg = 0;
+ u32 reg1 = 0;
+ u32 reg2 = 0;
+ u32 l, r;
+ u32 ll, rr;
+ int i;
+
+ reg = __raw_readl(REGS_AUDIOIN_BASE + \
+ HW_AUDIOIN_ADCVOLUME);
+
+ reg1 = reg & ~BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT;
+ reg1 = reg1 & ~BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT;
+ /* minimize adc volume */
+ reg2 = reg1 |
+ BF_AUDIOIN_ADCVOLUME_VOLUME_LEFT(ADC_VOLUME_MIN) |
+ BF_AUDIOIN_ADCVOLUME_VOLUME_RIGHT(ADC_VOLUME_MIN);
+ __raw_writel(reg2,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME);
+ msleep(1);
+
+ l = (reg & BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT) >>
+ BP_AUDIOIN_ADCVOLUME_VOLUME_LEFT;
+ r = (reg & BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT) >>
+ BP_AUDIOIN_ADCVOLUME_VOLUME_RIGHT;
+
+ /* fade in adc vol */
+ for (i = ADC_VOLUME_MIN; (i < l) || (i < r);) {
+ i += 0x8;
+ ll = i < l ? i : l;
+ rr = i < r ? i : r;
+ reg2 = reg1 |
+ BF_AUDIOIN_ADCVOLUME_VOLUME_LEFT(ll) |
+ BF_AUDIOIN_ADCVOLUME_VOLUME_RIGHT(rr);
+ __raw_writel(reg2,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME);
+ msleep(1);
+ }
+ adc_ramp_done = 1;
+}
+
+static void mxs_dac_schedule_ramp_work(struct delayed_work *work)
+{
+ schedule_delayed_work(work, msecs_to_jiffies(2));
+ dac_ramp_done = 0;
+}
+
+static void mxs_dac_ramp_work(struct work_struct *work)
+{
+ u32 reg = 0;
+ u32 reg1 = 0;
+ u32 l, r;
+ u32 ll, rr;
+ int i;
+
+ /* unmute hp and speaker */
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_CLR);
+ __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_CLR);
+
+ reg = __raw_readl(REGS_AUDIOOUT_BASE + \
+ HW_AUDIOOUT_HPVOL);
+
+ reg1 = reg & ~BM_AUDIOOUT_HPVOL_VOL_LEFT;
+ reg1 = reg1 & ~BM_AUDIOOUT_HPVOL_VOL_RIGHT;
+
+ l = (reg & BM_AUDIOOUT_HPVOL_VOL_LEFT) >>
+ BP_AUDIOOUT_HPVOL_VOL_LEFT;
+ r = (reg & BM_AUDIOOUT_HPVOL_VOL_RIGHT) >>
+ BP_AUDIOOUT_HPVOL_VOL_RIGHT;
+ /* fade in hp vol */
+ for (i = 0x7f; i > 0 ;) {
+ i -= 0x8;
+ ll = i > (int)l ? i : l;
+ rr = i > (int)r ? i : r;
+ reg = reg1 | BF_AUDIOOUT_HPVOL_VOL_LEFT(ll)
+ | BF_AUDIOOUT_HPVOL_VOL_RIGHT(rr);
+ __raw_writel(reg,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL);
+ msleep(1);
+ }
+ dac_ramp_done = 1;
+}
+
+static irqreturn_t mxs_short_irq(int irq, void *dev_id)
+{
+ __raw_writel(BM_AUDIOOUT_ANACTRL_SHORTMODE_LR,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_LR_STS,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ __raw_writel(BF_AUDIOOUT_ANACTRL_SHORTMODE_LR(0x1),
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET);
+ __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+ __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+
+ mxs_adc_schedule_work(&work);
+ return IRQ_HANDLED;
+}
static irqreturn_t mxs_err_irq(int irq, void *dev_id)
{
struct snd_pcm_substream *substream = dev_id;
@@ -104,68 +265,47 @@ static int mxs_adc_trigger(struct snd_pcm_substream *substream,
{
int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
int ret = 0;
- u32 reg = 0;
- u32 reg1 = 0;
- u32 l, r;
- u32 ll, rr;
- int i;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
if (playback) {
- reg = __raw_readl(REGS_AUDIOOUT_BASE + \
- HW_AUDIOOUT_HPVOL);
- reg1 = BM_AUDIOOUT_HPVOL_VOL_LEFT | \
- BM_AUDIOOUT_HPVOL_VOL_RIGHT;
- __raw_writel(reg1, REGS_AUDIOOUT_BASE + \
- HW_AUDIOOUT_HPVOL);
-
- __raw_writel(BM_AUDIOOUT_CTRL_RUN,
+ /* enable the fifo error interrupt */
+ __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN,
REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
- __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND,
- REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
-
- reg1 = reg & ~BM_AUDIOOUT_HPVOL_VOL_LEFT;
- reg1 = reg1 & ~BM_AUDIOOUT_HPVOL_VOL_RIGHT;
-
- l = (reg & BM_AUDIOOUT_HPVOL_VOL_LEFT) >>
- BP_AUDIOOUT_HPVOL_VOL_LEFT;
- r = (reg & BM_AUDIOOUT_HPVOL_VOL_RIGHT) >>
- BP_AUDIOOUT_HPVOL_VOL_RIGHT;
- for (i = 0x7f; i > 0 ; i -= 0x8) {
- ll = i > l ? i : l;
- rr = i > r ? i : r;
- /* fade in hp vol */
- reg = reg1 | BF_AUDIOOUT_HPVOL_VOL_LEFT(ll)
- | BF_AUDIOOUT_HPVOL_VOL_RIGHT(rr);
- __raw_writel(reg,
- REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL);
- udelay(100);
- }
- __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE,
- REGS_AUDIOIN_BASE + HW_AUDIOOUT_SPEAKERCTRL_CLR);
- }
- else
+ /* write a data to data reg to trigger the transfer */
+ __raw_writel(0x0,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DATA);
+ mxs_dac_schedule_ramp_work(&dac_ramp_work);
+ } else {
__raw_writel(BM_AUDIOIN_CTRL_RUN,
REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
-
+ mxs_adc_schedule_ramp_work(&adc_ramp_work);
+ }
break;
case SNDRV_PCM_TRIGGER_STOP:
if (playback) {
- __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND,
- REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+ if (dac_ramp_done == 0) {
+ cancel_delayed_work(&dac_ramp_work);
+ dac_ramp_done = 1;
+ }
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET);
__raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE,
- REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET);
-
- __raw_writel(BM_AUDIOOUT_CTRL_RUN,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET);
+ /* disable the fifo error interrupt */
+ __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN,
REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
- }
- else
+ } else {
+ if (adc_ramp_done == 0) {
+ cancel_delayed_work(&adc_ramp_work);
+ adc_ramp_done = 1;
+ }
__raw_writel(BM_AUDIOIN_CTRL_RUN,
REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+ }
break;
case SNDRV_PCM_TRIGGER_RESUME:
@@ -187,8 +327,13 @@ static int mxs_adc_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
int irq;
+ int irq_short;
int ret;
+ INIT_DELAYED_WORK(&work, mxs_adc_work);
+ INIT_DELAYED_WORK(&adc_ramp_work, mxs_adc_ramp_work);
+ INIT_DELAYED_WORK(&dac_ramp_work, mxs_dac_ramp_work);
+
if (playback) {
irq = IRQ_DAC_ERROR;
cpu_dai->dma_data = &mxs_audio_out;
@@ -205,14 +350,21 @@ static int mxs_adc_startup(struct snd_pcm_substream *substream,
return ret;
}
+ irq_short = IRQ_HEADPHONE_SHORT;
+ ret = request_irq(irq_short, mxs_short_irq,
+ IRQF_DISABLED | IRQF_SHARED, "MXS DAC/ADC HP SHORT", substream);
+ if (ret) {
+ printk(KERN_ERR "%s: Unable to request ADC/DAC HP SHORT irq %d\n",
+ __func__, IRQ_DAC_ERROR);
+ return ret;
+ }
+
/* Enable error interrupt */
if (playback) {
__raw_writel(BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ,
REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
__raw_writel(BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ,
REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
- __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN,
- REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
} else {
__raw_writel(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ,
REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
diff --git a/sound/soc/mxs/mxs-dai.c b/sound/soc/mxs/mxs-dai.c
index a548b9948516..9ca22ecdb4b0 100644
--- a/sound/soc/mxs/mxs-dai.c
+++ b/sound/soc/mxs/mxs-dai.c
@@ -288,6 +288,7 @@ static int mxs_saif_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
default:
return -EINVAL;
}
+ break;
default:
return -EINVAL;
}
diff --git a/sound/soc/mxs/mxs-evk-adc.c b/sound/soc/mxs/mxs-evk-adc.c
index bd1ed6bf691f..16c1cb4fab96 100644
--- a/sound/soc/mxs/mxs-evk-adc.c
+++ b/sound/soc/mxs/mxs-evk-adc.c
@@ -33,12 +33,119 @@
#include "mxs-adc.h"
#include "mxs-pcm.h"
+/* mxs evk machine connections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* HPR/HPL OUT --> Headphone Jack */
+ {"Headphone Jack", NULL, "HPR"},
+ {"Headphone Jack", NULL, "HPL"},
+
+ /* SPEAKER OUT --> Ext Speaker */
+ {"Ext Spk", NULL, "SPEAKER"},
+};
+
+static int mxs_evk_jack_func;
+static int mxs_evk_spk_func;
+
+static const char *jack_function[] = { "off", "on"};
+
+static const char *spk_function[] = { "off", "on" };
+
+
+static const struct soc_enum mxs_evk_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static int mxs_evk_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = mxs_evk_jack_func;
+ return 0;
+}
+
+static int mxs_evk_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (mxs_evk_jack_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ mxs_evk_jack_func = ucontrol->value.enumerated.item[0];
+ if (mxs_evk_jack_func)
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ else
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+
+ snd_soc_dapm_sync(codec);
+ return 1;
+}
+
+static int mxs_evk_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = mxs_evk_spk_func;
+ return 0;
+}
+
+static int mxs_evk_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (mxs_evk_spk_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ mxs_evk_spk_func = ucontrol->value.enumerated.item[0];
+ if (mxs_evk_spk_func)
+ snd_soc_dapm_enable_pin(codec, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(codec, "Ext Spk");
+
+ snd_soc_dapm_sync(codec);
+ return 1;
+}
+/* mxs evk card dapm widgets */
+static const struct snd_soc_dapm_widget mxs_evk_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static const struct snd_kcontrol_new mxs_evk_controls[] = {
+ SOC_ENUM_EXT("HP Playback Switch", mxs_evk_enum[0], mxs_evk_get_jack,
+ mxs_evk_set_jack),
+ SOC_ENUM_EXT("Speaker Playback Switch", mxs_evk_enum[1],
+ mxs_evk_get_spk, mxs_evk_set_spk),
+};
+
+static int mxs_evk_codec_init(struct snd_soc_codec *codec)
+{
+ int i, ret;
+ /* Add mxs evk specific controls */
+ snd_soc_add_controls(codec, mxs_evk_controls,
+ ARRAY_SIZE(mxs_evk_controls));
+
+ /* Add mxs evk specific widgets */
+ snd_soc_dapm_new_controls(codec, mxs_evk_dapm_widgets,
+ ARRAY_SIZE(mxs_evk_dapm_widgets));
+
+ /* Set up mxs evk specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(codec);
+ /* default on */
+ mxs_evk_jack_func = 1;
+ mxs_evk_spk_func = 1;
+
+ return ret;
+}
/* mxs evk dac/adc audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link mxs_evk_codec_dai = {
.name = "MXS ADC/DAC",
.stream_name = "MXS ADC/DAC",
.cpu_dai = &mxs_adc_dai,
.codec_dai = &mxs_codec_dai,
+ .init = mxs_evk_codec_init,
};
/* mxs evk audio machine driver */
diff --git a/sound/soc/mxs/mxs-pcm.c b/sound/soc/mxs/mxs-pcm.c
index a9f0358687f4..dac91e3eb8fb 100644
--- a/sound/soc/mxs/mxs-pcm.c
+++ b/sound/soc/mxs/mxs-pcm.c
@@ -141,6 +141,7 @@ static int mxs_pcm_prepare(struct snd_pcm_substream *substream)
/* Link with previous command */
prtd->dma_desc_array[i]->cmd.cmd.bits.bytes = prtd->dma_period;
prtd->dma_desc_array[i]->cmd.cmd.bits.irq = 1;
+ prtd->dma_desc_array[i]->cmd.cmd.bits.dec_sem = 0;
prtd->dma_desc_array[i]->cmd.cmd.bits.chain = 1;
/* Set DMA direction */
if (playback)
@@ -194,6 +195,8 @@ static void mxs_pcm_stop(struct snd_pcm_substream *substream)
prtd->dma_desc_array[(desc + 1)%8]->cmd.cmd.bits.command = NO_DMA_XFER;
mxs_dma_unfreeze(prtd->dma_ch);
+
+ mxs_dma_disable(prtd->dma_ch);
}
static int mxs_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
@@ -375,6 +378,7 @@ static int mxs_pcm_close(struct snd_pcm_substream *substream)
free_irq(prtd->params->irq, substream);
mxs_dma_get_cooked(prtd->dma_ch, &list);
/* Free DMA channel*/
+ mxs_dma_reset(prtd->dma_ch);
for (desc = 0; desc < desc_num; desc++)
mxs_dma_free_desc(prtd->dma_desc_array[desc]);
mxs_dma_release(prtd->dma_ch, mxs_pcm_dev);