diff options
author | Rob Herring <r.herring@freescale.com> | 2009-05-05 14:25:13 -0500 |
---|---|---|
committer | Rob Herring <r.herring@freescale.com> | 2009-05-05 18:17:51 -0500 |
commit | c096fc73812248072b8c62a071dfe565fa359983 (patch) | |
tree | 1b67d3591a8e9d009ca5fcf37d8de654f9cdf33f /sound | |
parent | 4e9da5716840fa8458211b98191ccdebcec74a67 (diff) |
ENGR00112199 Import EA 3780 release 4
This is from EA P4 release with the following changes:
Ported to 2.6.28
UBI support is stock 2.6.28.
USB is not integrated
Regulator code is not yet ported.
Removed 3700 specific files
Fix copyrights
Signed-off-by: Rob Herring <r.herring@freescale.com>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/Kconfig | 3 | ||||
-rw-r--r-- | sound/soc/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/codecs/Kconfig | 8 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 4 | ||||
-rw-r--r-- | sound/soc/codecs/stmp378x_codec.c | 775 | ||||
-rw-r--r-- | sound/soc/codecs/stmp378x_codec.h | 87 | ||||
-rw-r--r-- | sound/soc/codecs/stmp3xxx_spdif.c | 412 | ||||
-rw-r--r-- | sound/soc/codecs/stmp3xxx_spdif.h | 37 | ||||
-rw-r--r-- | sound/soc/stmp3xxx/Kconfig | 31 | ||||
-rw-r--r-- | sound/soc/stmp3xxx/Makefile | 15 | ||||
-rw-r--r-- | sound/soc/stmp3xxx/stmp3780_devb.c | 97 | ||||
-rw-r--r-- | sound/soc/stmp3xxx/stmp3780_devb_spdif.c | 95 | ||||
-rw-r--r-- | sound/soc/stmp3xxx/stmp3xxx_dai.c | 224 | ||||
-rw-r--r-- | sound/soc/stmp3xxx/stmp3xxx_dai.h | 21 | ||||
-rw-r--r-- | sound/soc/stmp3xxx/stmp3xxx_pcm.c | 440 | ||||
-rw-r--r-- | sound/soc/stmp3xxx/stmp3xxx_pcm.h | 30 | ||||
-rw-r--r-- | sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c | 184 | ||||
-rw-r--r-- | sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h | 21 |
18 files changed, 2484 insertions, 2 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 2d8f05baf721..1e48ea18e5f8 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -10,7 +10,7 @@ menuconfig SND_SOC If you want ASoC support, you should say Y here and also to the specific driver for your SoC platform below. - + ASoC provides power efficient ALSA support for embedded battery powered SoC based systems like PDA's, Phones and Personal Media Players. @@ -30,6 +30,7 @@ source "sound/soc/pxa/Kconfig" source "sound/soc/s3c24xx/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/imx/Kconfig" +source "sound/soc/stmp3xxx/Kconfig" source "sound/soc/fsl/Kconfig" source "sound/soc/davinci/Kconfig" source "sound/soc/omap/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 850776caea15..d997c29d2d8a 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -1,5 +1,5 @@ snd-soc-core-objs := soc-core.o soc-dapm.o obj-$(CONFIG_SND_SOC) += snd-soc-core.o -obj-$(CONFIG_SND_SOC) += codecs/ at32/ at91/ pxa/ s3c24xx/ sh/ fsl/ davinci/ imx/ +obj-$(CONFIG_SND_SOC) += codecs/ at32/ at91/ pxa/ s3c24xx/ sh/ fsl/ davinci/ imx/ stmp3xxx/ obj-$(CONFIG_SND_SOC) += omap/ au1x/ blackfin/ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index cf0fcb03ef69..45a12880d3bb 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -122,3 +122,11 @@ config SND_SOC_SGTL5000 config SND_SOC_AK4647 tristate depends on I2C + +config SND_SOC_STMP378X_CODEC + tristate + depends on SND_SOC + +config SND_SOC_STMP3XXX_SPDIF + tristate + depends on SND_SOC diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index e47aa87358ce..b5e5b51bc4e1 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -22,6 +22,8 @@ snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-sgtl5000-objs := sgtl5000.o snd-soc-ak4647-objs := ak4647.o +snd-soc-stmp378x-codec-objs := stmp378x_codec.o +snd-soc-stmp3xxx-spdif-objs := stmp3xxx_spdif.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o @@ -47,3 +49,5 @@ obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o obj-$(CONFIG_SND_SOC_AK4647) += snd-soc-ak4647.o +obj-$(CONFIG_SND_SOC_STMP378X_CODEC) += snd-soc-stmp378x-codec.o +obj-$(CONFIG_SND_SOC_STMP3XXX_SPDIF) += snd-soc-stmp3xxx-spdif.o diff --git a/sound/soc/codecs/stmp378x_codec.c b/sound/soc/codecs/stmp378x_codec.c new file mode 100644 index 000000000000..0893894efef5 --- /dev/null +++ b/sound/soc/codecs/stmp378x_codec.c @@ -0,0 +1,775 @@ +/* + * ALSA codec for Freescale STMP378X ADC/DAC + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <asm/dma.h> + +#include <mach/regs-apbx.h> +#include <mach/regs-audioin.h> +#include <mach/regs-audioout.h> +#include <mach/regs-rtc.h> + +#include "stmp378x_codec.h" + +#define BV_AUDIOIN_ADCVOL_SELECT__MIC 0x00 /* missing define */ + +#define STMP378X_VERSION "0.1" +struct stmp378x_codec_priv { + struct device *dev; + struct clk *clk; +}; + +/* + * ALSA API + */ +static u32 adc_regmap[] = { + HW_AUDIOOUT_CTRL_ADDR, + HW_AUDIOOUT_STAT_ADDR, + HW_AUDIOOUT_DACSRR_ADDR, + HW_AUDIOOUT_DACVOLUME_ADDR, + HW_AUDIOOUT_DACDEBUG_ADDR, + HW_AUDIOOUT_HPVOL_ADDR, + HW_AUDIOOUT_PWRDN_ADDR, + HW_AUDIOOUT_REFCTRL_ADDR, + HW_AUDIOOUT_ANACTRL_ADDR, + HW_AUDIOOUT_TEST_ADDR, + HW_AUDIOOUT_BISTCTRL_ADDR, + HW_AUDIOOUT_BISTSTAT0_ADDR, + HW_AUDIOOUT_BISTSTAT1_ADDR, + HW_AUDIOOUT_ANACLKCTRL_ADDR, + HW_AUDIOOUT_DATA_ADDR, + HW_AUDIOOUT_SPEAKERCTRL_ADDR, + HW_AUDIOOUT_VERSION_ADDR, + HW_AUDIOIN_CTRL_ADDR, + HW_AUDIOIN_STAT_ADDR, + HW_AUDIOIN_ADCSRR_ADDR, + HW_AUDIOIN_ADCVOLUME_ADDR, + HW_AUDIOIN_ADCDEBUG_ADDR, + HW_AUDIOIN_ADCVOL_ADDR, + HW_AUDIOIN_MICLINE_ADDR, + HW_AUDIOIN_ANACLKCTRL_ADDR, + HW_AUDIOIN_DATA_ADDR, +}; + +/* + * ALSA core supports only 16 bit registers. It means we have to simulate it + * by virtually splitting a 32bit ADC/DAC registers into two halves + * high (bits 31:16) and low (bits 15:0). The routins abow detects which part + * of 32bit register is accessed. + */ +static int stmp378x_codec_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + unsigned int reg_val; + unsigned int mask = 0xffff; + + if (reg >= ADC_REGNUM) + return -EIO; + + if (reg & 0x1) { + mask <<= 16; + value <<= 16; + } + + reg_val = __raw_readl(adc_regmap[reg >> 1]); + reg_val = (reg_val & ~mask) | value; + __raw_writel(reg_val, adc_regmap[reg >> 1]); + + return 0; +} + +static unsigned int stmp378x_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int reg_val; + + if (reg >= ADC_REGNUM) + return -1; + + reg_val = __raw_readl(adc_regmap[reg >> 1]); + if (reg & 1) + reg_val >>= 16; + + return reg_val & 0xffff; +} + +static const char *stmp378x_codec_adc_input_sel[] = + {"Mic", "Line In 1", "Head Phone", "Line In 2"}; + +static const char *stmp378x_codec_hp_output_sel[] = + {"DAC", "Line In 1"}; + +static const char *stmp378x_codec_adc_3d_sel[] = + {"Off", "Low", "Medium", "High"}; + +static const struct soc_enum stmp378x_codec_enum[] = { + SOC_ENUM_SINGLE(ADC_ADCVOL_L, 12, 4, stmp378x_codec_adc_input_sel), + SOC_ENUM_SINGLE(ADC_ADCVOL_L, 4, 4, stmp378x_codec_adc_input_sel), + SOC_ENUM_SINGLE(DAC_HPVOL_H, 0, 2, stmp378x_codec_hp_output_sel), + SOC_ENUM_SINGLE(DAC_CTRL_L, 8, 4, stmp378x_codec_adc_3d_sel), +}; + +/* Codec controls */ +static const struct snd_kcontrol_new stmp378x_snd_controls[] = { + /* Playback Volume */ + SOC_DOUBLE_R("DAC Playback Volume", + DAC_VOLUME_H, DAC_VOLUME_L, 0, 0xFF, 0), + SOC_DOUBLE_R("DAC Playback Switch", + DAC_VOLUME_H, DAC_VOLUME_L, 8, 0x01, 1), + SOC_DOUBLE("HP Playback Volume", DAC_HPVOL_L, 8, 0, 0x7F, 1), + SOC_SINGLE("HP Playback Switch", DAC_HPVOL_H, 8, 0x1, 1), + SOC_SINGLE("Speaker Playback Switch", DAC_SPEAKERCTRL_H, 8, 0x1, 1), + + /* Capture Volume */ + SOC_DOUBLE_R("ADC Capture Volume", + ADC_VOLUME_H, ADC_VOLUME_L, 0, 0xFF, 0), + SOC_DOUBLE("ADC PGA Capture Volume", ADC_ADCVOL_L, 8, 0, 0x0F, 0), + SOC_SINGLE("ADC PGA Capture Switch", ADC_ADCVOL_H, 8, 0x1, 1), + SOC_SINGLE("Mic PGA Capture Volume", ADC_MICLINE_L, 0, 0x03, 0), + + /* Virtual 3D effect */ + SOC_ENUM("3D effect", stmp378x_codec_enum[3]), +}; + +/* add non dapm controls */ +static int stmp378x_codec_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(stmp378x_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&stmp378x_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Left ADC Mux */ +static const struct snd_kcontrol_new stmp378x_left_adc_controls = +SOC_DAPM_ENUM("Route", stmp378x_codec_enum[0]); + +/* Right ADC Mux */ +static const struct snd_kcontrol_new stmp378x_right_adc_controls = +SOC_DAPM_ENUM("Route", stmp378x_codec_enum[1]); + +/* Head Phone Mux */ +static const struct snd_kcontrol_new stmp378x_hp_controls = +SOC_DAPM_ENUM("Route", stmp378x_codec_enum[2]); + +static const struct snd_soc_dapm_widget stmp378x_codec_widgets[] = { + + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &stmp378x_left_adc_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &stmp378x_right_adc_controls), + SND_SOC_DAPM_MUX("HP Mux", SND_SOC_NOPM, 0, 0, + &stmp378x_hp_controls), + + SND_SOC_DAPM_INPUT("LINE1L"), + SND_SOC_DAPM_INPUT("LINE1R"), + SND_SOC_DAPM_INPUT("LINE2L"), + SND_SOC_DAPM_INPUT("LINE2R"), + SND_SOC_DAPM_INPUT("MIC"), + + SND_SOC_DAPM_OUTPUT("SPEAKER"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), +}; +static const struct snd_soc_dapm_route intercon[] = { + + /* Left ADC Mux */ + {"Left ADC Mux", "Mic", "MIC"}, + {"Left ADC Mux", "Line In 1", "LINE1L"}, + {"Left ADC Mux", "Line In 2", "LINE2L"}, + {"Left ADC Mux", "Head Phone", "HPL"}, + + /* Right ADC Mux */ + {"Right ADC Mux", "Mic", "MIC"}, + {"Right ADC Mux", "Line In 1", "LINE1R"}, + {"Right ADC Mux", "Line In 2", "LINE2R"}, + {"Right ADC Mux", "Head Phone", "HPR"}, + + /* ADC */ + {"Left ADC", NULL, "Left ADC Mux"}, + {"Right ADC", NULL, "Right ADC Mux"}, + + /* HP Mux */ + {"HP Mux", "DAC", "Left DAC"}, + {"HP Mux", "DAC", "Right DAC"}, + {"HP Mux", "Line In 1", "LINE1L"}, + {"HP Mux", "Line In 1", "LINE1R"}, + + /* HP output */ + {"HPR", NULL, "HP Mux"}, + {"HPL", NULL, "HP Mux"}, + + /* Speaker amp */ + {"SPEAKER", NULL, "Right DAC"}, +}; + +static int stmp378x_codec_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, stmp378x_codec_widgets, + ARRAY_SIZE(stmp378x_codec_widgets)); + + /* set up audio path interconnects */ + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct dac_srr { + u32 rate; + u32 basemult; + u32 src_hold; + u32 src_int; + u32 src_frac; +}; + +static struct dac_srr srr_values[] = { + {192000, 0x4, 0x0, 0x0F, 0x13FF}, + {176400, 0x4, 0x0, 0x11, 0x0037}, + {128000, 0x4, 0x0, 0x17, 0x0E00}, + {96000, 0x2, 0x0, 0x0F, 0x13FF}, + {88200, 0x2, 0x0, 0x11, 0x0037}, + {64000, 0x2, 0x0, 0x17, 0x0E00}, + {48000, 0x1, 0x0, 0x0F, 0x13FF}, + {44100, 0x1, 0x0, 0x11, 0x0037}, + {32000, 0x1, 0x0, 0x17, 0x0E00}, + {24000, 0x1, 0x1, 0x0F, 0x13FF}, + {22050, 0x1, 0x1, 0x11, 0x0037}, + {16000, 0x1, 0x1, 0x17, 0x0E00}, + {12000, 0x1, 0x3, 0x0F, 0x13FF}, + {11025, 0x1, 0x3, 0x11, 0x0037}, + {8000, 0x1, 0x3, 0x17, 0x0E00} +}; + +static inline int get_srr_values(int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(srr_values); i++) + if (srr_values[i].rate == rate) + return i; + + return -1; +} + +static int stmp378x_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int i; + u32 srr_value = 0; + u32 src_hold = 0; + + i = get_srr_values(params_rate(params)); + if (i < 0) + printk(KERN_WARNING "%s doesn't support rate %d\n", + codec->name, params_rate(params)); + else { + src_hold = srr_values[i].src_hold; + + srr_value = + BF_AUDIOOUT_DACSRR_BASEMULT(srr_values[i].basemult) | + BF_AUDIOOUT_DACSRR_SRC_INT(srr_values[i].src_int) | + BF_AUDIOOUT_DACSRR_SRC_FRAC(srr_values[i].src_frac) | + BF_AUDIOOUT_DACSRR_SRC_HOLD(src_hold); + + if (playback) + HW_AUDIOOUT_DACSRR_WR(srr_value); + else + HW_AUDIOIN_ADCSRR_WR(srr_value); + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (playback) + HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_WORD_LENGTH); + else + HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_WORD_LENGTH); + + break; + + case SNDRV_PCM_FORMAT_S32_LE: + if (playback) + HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_WORD_LENGTH); + else + HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_WORD_LENGTH); + + break; + + default: + printk(KERN_WARNING "%s doesn't support format %d\n", + codec->name, params_format(params)); + + } + + return 0; +} + +static int stmp378x_codec_dig_mute(struct snd_soc_dai *dai, int mute) +{ + u32 dac_mask = BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT; + + if (mute) + HW_AUDIOOUT_DACVOLUME_SET(dac_mask); + else + HW_AUDIOOUT_DACVOLUME_CLR(dac_mask); + + return 0; +} + +/* + * Codec initialization + */ +#define VAG_BASE_VALUE ((1400/2 - 625)/25) +static void stmp378x_codec_dac_set_vag(void) +{ + u32 refctrl_val = HW_AUDIOOUT_REFCTRL_RD(); + + refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VAG_VAL); + refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VBG_ADJ); + refctrl_val |= BF_AUDIOOUT_REFCTRL_VAG_VAL(VAG_BASE_VALUE) | + BM_AUDIOOUT_REFCTRL_ADJ_VAG | + BF_AUDIOOUT_REFCTRL_ADC_REFVAL(0xF) | + BM_AUDIOOUT_REFCTRL_ADJ_ADC | + BF_AUDIOOUT_REFCTRL_VBG_ADJ(0x3) | + BM_AUDIOOUT_REFCTRL_RAISE_REF; + + HW_AUDIOOUT_REFCTRL_WR(refctrl_val); +} + +static void +stmp378x_codec_dac_power_on(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Ungate DAC clocks */ + HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_CLKGATE); + HW_AUDIOOUT_ANACLKCTRL_CLR(BM_AUDIOOUT_ANACLKCTRL_CLKGATE); + + /* Set capless mode */ + HW_AUDIOOUT_PWRDN_CLR(BM_AUDIOOUT_PWRDN_CAPLESS); + + /* 16 bit word length */ + HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_WORD_LENGTH); + + /* Power up DAC */ + HW_AUDIOOUT_PWRDN_CLR(BM_AUDIOOUT_PWRDN_DAC); + /* Update DAC volume over zero crossings */ + HW_AUDIOOUT_DACVOLUME_SET(BM_AUDIOOUT_DACVOLUME_EN_ZCD); + /* Mute DAC */ + HW_AUDIOOUT_DACVOLUME_SET(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT); + + /* Update HP volume over zero crossings */ + HW_AUDIOOUT_HPVOL_SET(BM_AUDIOOUT_HPVOL_EN_MSTR_ZCD); + + /* Power up HP output */ + HW_AUDIOOUT_ANACTRL_SET(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND); + HW_RTC_PERSISTENT0_SET(BF_RTC_PERSISTENT0_SPARE_ANALOG(0x2)); + HW_AUDIOOUT_PWRDN_CLR(BM_AUDIOOUT_PWRDN_HEADPHONE); + HW_AUDIOOUT_ANACTRL_SET(BM_AUDIOOUT_ANACTRL_HP_CLASSAB); + HW_AUDIOOUT_ANACTRL_CLR(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND); + /* Mute HP output */ + HW_AUDIOOUT_HPVOL_SET(BM_AUDIOOUT_HPVOL_MUTE); + + /* Power up speaker amp */ + HW_AUDIOOUT_PWRDN_CLR(BM_AUDIOOUT_PWRDN_SPEAKER); + /* Mute speaker amp */ + HW_AUDIOOUT_SPEAKERCTRL_SET(BM_AUDIOOUT_SPEAKERCTRL_MUTE); +} + +static void +stmp378x_codec_dac_power_down(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Disable class AB */ + HW_AUDIOOUT_ANACTRL_CLR(BM_AUDIOOUT_ANACTRL_HP_CLASSAB); + + /* Set hold to ground */ + HW_AUDIOOUT_ANACTRL_SET(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND); + + /* Mute HP output */ + HW_AUDIOOUT_HPVOL_SET(BM_AUDIOOUT_HPVOL_MUTE); + /* Power down HP output */ + HW_AUDIOOUT_PWRDN_SET(BM_AUDIOOUT_PWRDN_HEADPHONE); + + /* Mute speaker amp */ + HW_AUDIOOUT_SPEAKERCTRL_SET(BM_AUDIOOUT_SPEAKERCTRL_MUTE); + /* Power down speaker amp */ + HW_AUDIOOUT_PWRDN_SET(BM_AUDIOOUT_PWRDN_SPEAKER); + + /* Mute DAC */ + HW_AUDIOOUT_DACVOLUME_SET(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT); + /* Power down DAC */ + HW_AUDIOOUT_PWRDN_SET(BM_AUDIOOUT_PWRDN_DAC); + + /* Gate DAC clocks */ + HW_AUDIOOUT_ANACLKCTRL_SET(BM_AUDIOOUT_ANACLKCTRL_CLKGATE); + HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_CLKGATE); +} + +static void +stmp378x_codec_adc_power_on(struct stmp378x_codec_priv *stmp378x_adc) +{ + u32 reg; + + /* Ungate ADC clocks */ + HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_CLKGATE); + HW_AUDIOIN_ANACLKCTRL_CLR(BM_AUDIOIN_ANACLKCTRL_CLKGATE); + + /* Power Up ADC */ + HW_AUDIOOUT_PWRDN_CLR( + BM_AUDIOOUT_PWRDN_ADC | BM_AUDIOOUT_PWRDN_RIGHT_ADC); + + /* 16 bit word length */ + HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_WORD_LENGTH); + + /* Unmute ADC channels */ + HW_AUDIOIN_ADCVOL_CLR(BM_AUDIOIN_ADCVOL_MUTE); + + /* + * The MUTE_LEFT and MUTE_RIGHT fields need to be cleared. + * They aren't presented in the datasheet, so this is hardcode. + */ + HW_AUDIOIN_ADCVOLUME_CLR(0x01000100); + + /* Set the Input channel gain 3dB */ + HW_AUDIOIN_ADCVOL_CLR(BM_AUDIOIN_ADCVOL_GAIN_LEFT); + HW_AUDIOIN_ADCVOL_CLR(BM_AUDIOIN_ADCVOL_GAIN_RIGHT); + HW_AUDIOIN_ADCVOL_SET(BF_AUDIOIN_ADCVOL_GAIN_LEFT(2)); + HW_AUDIOIN_ADCVOL_SET(BF_AUDIOIN_ADCVOL_GAIN_RIGHT(2)); + + /* Select default input - Microphone */ + HW_AUDIOIN_ADCVOL_CLR(BM_AUDIOIN_ADCVOL_SELECT_LEFT); + HW_AUDIOIN_ADCVOL_CLR(BM_AUDIOIN_ADCVOL_SELECT_RIGHT); + HW_AUDIOIN_ADCVOL_SET( + BF_AUDIOIN_ADCVOL_SELECT_LEFT(BV_AUDIOIN_ADCVOL_SELECT__MIC)); + HW_AUDIOIN_ADCVOL_SET( + BF_AUDIOIN_ADCVOL_SELECT_RIGHT(BV_AUDIOIN_ADCVOL_SELECT__MIC)); + + /* Set max ADC volume */ + reg = HW_AUDIOIN_ADCVOLUME_RD(); + reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT; + reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT; + reg |= BF_AUDIOIN_ADCVOLUME_VOLUME_LEFT(ADC_VOLUME_MAX); + reg |= BF_AUDIOIN_ADCVOLUME_VOLUME_RIGHT(ADC_VOLUME_MAX); + HW_AUDIOIN_ADCVOLUME_WR(reg); +} + +static void +stmp378x_codec_adc_power_down(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Mute ADC channels */ + HW_AUDIOIN_ADCVOL_SET(BM_AUDIOIN_ADCVOL_MUTE); + + /* Power Down ADC */ + HW_AUDIOOUT_PWRDN_SET( + BM_AUDIOOUT_PWRDN_ADC | BM_AUDIOOUT_PWRDN_RIGHT_ADC); + + /* Gate ADC clocks */ + HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_CLKGATE); + HW_AUDIOIN_ANACLKCTRL_SET(BM_AUDIOIN_ANACLKCTRL_CLKGATE); +} + +static void +stmp378x_codec_dac_enable(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Move DAC codec out of reset */ + HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_SFTRST); + + /* Reduce analog power */ + HW_AUDIOOUT_TEST_CLR(BM_AUDIOOUT_TEST_HP_I1_ADJ); + HW_AUDIOOUT_TEST_SET(BF_AUDIOOUT_TEST_HP_I1_ADJ(0x1)); + HW_AUDIOOUT_REFCTRL_SET(BM_AUDIOOUT_REFCTRL_LOW_PWR); + HW_AUDIOOUT_REFCTRL_SET(BM_AUDIOOUT_REFCTRL_XTAL_BGR_BIAS); + HW_AUDIOOUT_REFCTRL_CLR(BM_AUDIOOUT_REFCTRL_BIAS_CTRL); + HW_AUDIOOUT_REFCTRL_CLR(BF_AUDIOOUT_REFCTRL_BIAS_CTRL(0x1)); + + /* Set Vag value */ + stmp378x_codec_dac_set_vag(); + + /* Power on DAC codec */ + stmp378x_codec_dac_power_on(stmp378x_adc); +} + +static void stmp378x_codec_dac_disable(struct stmp378x_codec_priv *stmp378x_adc) +{ + stmp378x_codec_dac_power_down(stmp378x_adc); +} + +static void +stmp378x_codec_adc_enable(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Move ADC codec out of reset */ + HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_SFTRST); + + /* Power on ADC codec */ + stmp378x_codec_adc_power_on(stmp378x_adc); +} + +static void stmp378x_codec_adc_disable(struct stmp378x_codec_priv *stmp378x_adc) +{ + stmp378x_codec_adc_power_down(stmp378x_adc); +} + +static void +stmp378x_codec_init(struct snd_soc_codec *codec) +{ + struct stmp378x_codec_priv *stmp378x_adc = codec->private_data; + + /* Soft reset DAC block */ + HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_SFTRST); + while (!(HW_AUDIOOUT_CTRL_RD() & BM_AUDIOOUT_CTRL_CLKGATE)); + + /* Soft reset ADC block */ + HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_SFTRST); + while (!(HW_AUDIOIN_CTRL_RD() & BM_AUDIOIN_CTRL_CLKGATE)); + + stmp378x_codec_dac_enable(stmp378x_adc); + stmp378x_codec_adc_enable(stmp378x_adc); + + stmp378x_codec_add_controls(codec); + stmp378x_codec_add_widgets(codec); +} + +static void +stmp378x_codec_exit(struct snd_soc_codec *codec) +{ + struct stmp378x_codec_priv *stmp378x_adc = codec->private_data; + stmp378x_codec_dac_disable(stmp378x_adc); + stmp378x_codec_adc_disable(stmp378x_adc); +} + +#define STMP378X_ADC_RATES SNDRV_PCM_RATE_8000_192000 +#define STMP378X_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) +struct snd_soc_dai stmp378x_codec_dai = { + .name = "stmp378x adc/dac", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = STMP378X_ADC_RATES, + .formats = STMP378X_ADC_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = STMP378X_ADC_RATES, + .formats = STMP378X_ADC_FORMATS, + }, + .ops = { + .hw_params = stmp378x_codec_hw_params, + }, + .dai_ops = { + .digital_mute = stmp378x_codec_dig_mute, + } +}; +EXPORT_SYMBOL_GPL(stmp378x_codec_dai); + +static int stmp378x_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct stmp378x_codec_priv *stmp378x_adc; + int ret = 0; + + printk(KERN_INFO "STMP378X ADC/DAC Audio Codec %s\n", STMP378X_VERSION); + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + stmp378x_adc = kzalloc(sizeof(struct stmp378x_codec_priv), GFP_KERNEL); + if (stmp378x_adc == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->name = "stmp378x adc/dac"; + codec->owner = THIS_MODULE; + codec->private_data = stmp378x_adc; + codec->read = stmp378x_codec_read; + codec->write = stmp378x_codec_write; + codec->dai = &stmp378x_codec_dai; + codec->num_dai = 1; + socdev->codec = codec; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "%s: failed to create pcms\n", __func__); + goto pcm_err; + } + + /* Turn on audio clock */ + stmp378x_adc->dev = &pdev->dev; + stmp378x_adc->clk = clk_get(stmp378x_adc->dev, "audio"); + if (IS_ERR(stmp378x_adc->clk)) { + ret = PTR_ERR(stmp378x_adc->clk); + printk(KERN_ERR "%s: Clocks initialization failed\n", __func__); + goto clk_err; + } + clk_enable(stmp378x_adc->clk); + + stmp378x_codec_init(codec); + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "%s: failed to register card\n", __func__); + goto card_err; + } + + return ret; + +card_err: + clk_disable(stmp378x_adc->clk); + clk_put(stmp378x_adc->clk); +clk_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(socdev->codec); + return ret; +} + +static int stmp378x_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct stmp378x_codec_priv *stmp378x_adc; + + if (codec == NULL) + return 0; + + stmp378x_adc = codec->private_data; + + clk_disable(stmp378x_adc->clk); + clk_put(stmp378x_adc->clk); + + stmp378x_codec_exit(codec); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + kfree(socdev->codec); + + return 0; +} + +#ifdef CONFIG_PM +static int stmp378x_codec_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct stmp378x_codec_priv *stmp378x_adc; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + stmp378x_adc = codec->private_data; + + stmp378x_codec_dac_disable(stmp378x_adc); + stmp378x_codec_adc_disable(stmp378x_adc); + clk_disable(stmp378x_adc->clk); + ret = 0; + +out: + return ret; +} + +static int stmp378x_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct stmp378x_codec_priv *stmp378x_adc; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + stmp378x_adc = codec->private_data; + clk_enable(stmp378x_adc->clk); + + /* Soft reset DAC block */ + HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_SFTRST); + while (!(HW_AUDIOOUT_CTRL_RD() & BM_AUDIOOUT_CTRL_CLKGATE)); + + /* Soft reset ADC block */ + HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_SFTRST); + while (!(HW_AUDIOIN_CTRL_RD() & BM_AUDIOIN_CTRL_CLKGATE)); + + stmp378x_codec_dac_enable(stmp378x_adc); + stmp378x_codec_adc_enable(stmp378x_adc); + + ret = 0; + +out: + return ret; +} +#else +#define stmp378x_codec_suspend NULL +#define stmp378x_codec_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_codec_device soc_codec_dev_stmp378x = { + .probe = stmp378x_codec_probe, + .remove = stmp378x_codec_remove, + .suspend = stmp378x_codec_suspend, + .resume = stmp378x_codec_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_stmp378x); + +MODULE_DESCRIPTION("STMP378X ADC/DAC codec"); +MODULE_AUTHOR("Vladislav Buzov"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/stmp378x_codec.h b/sound/soc/codecs/stmp378x_codec.h new file mode 100644 index 000000000000..80fce7273126 --- /dev/null +++ b/sound/soc/codecs/stmp378x_codec.h @@ -0,0 +1,87 @@ +/* + * ALSA codec for Freescale STMP378X + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __STMP378X_CODEC_H +#define __STMP378X_CODEC_H + +#define DAC_CTRL_L 0 +#define DAC_CTRL_H 1 +#define DAC_STAT_L 2 +#define DAC_STAT_H 3 +#define DAC_SRR_L 4 +#define DAC_VOLUME_L 6 +#define DAC_VOLUME_H 7 +#define DAC_DEBUG_L 8 +#define DAC_DEBUG_H 9 +#define DAC_HPVOL_L 10 +#define DAC_HPVOL_H 11 +#define DAC_PWRDN_L 12 +#define DAC_PWRDN_H 13 +#define DAC_REFCTRL_L 14 +#define DAC_REFCTRL_H 15 +#define DAC_ANACTRL_L 16 +#define DAC_ANACTRL_H 17 +#define DAC_TEST_L 18 +#define DAC_TEST_H 19 +#define DAC_BISTCTRL_L 20 +#define DAC_BISTCTRL_H 21 +#define DAC_BISTSTAT0_L 22 +#define DAC_BISTSTAT0_H 23 +#define DAC_BISTSTAT1_L 24 +#define DAC_BISTSTAT1_H 25 +#define DAC_ANACLKCTRL_L 26 +#define DAC_ANACLKCTRL_H 27 +#define DAC_DATA_L 28 +#define DAC_DATA_H 29 +#define DAC_SPEAKERCTRL_L 30 +#define DAC_SPEAKERCTRL_H 31 +#define DAC_VERSION_L 32 +#define DAC_VERSION_H 33 +#define ADC_CTRL_L 34 +#define ADC_CTRL_H 35 +#define ADC_STAT_L 36 +#define ADC_STAT_H 37 +#define ADC_SRR_L 38 +#define ADC_SRR_H 39 +#define ADC_VOLUME_L 40 +#define ADC_VOLUME_H 41 +#define ADC_DEBUG_L 42 +#define ADC_DEBUG_H 43 +#define ADC_ADCVOL_L 44 +#define ADC_ADCVOL_H 45 +#define ADC_MICLINE_L 46 +#define ADC_MICLINE_H 47 +#define ADC_ANACLKCTRL_L 48 +#define ADC_ANACLKCTRL_H 49 +#define ADC_DATA_L 50 +#define ADC_DATA_H 51 + +#define ADC_REGNUM 52 + +#define DAC_VOLUME_MIN 0x37 +#define DAC_VOLUME_MAX 0xFE +#define ADC_VOLUME_MIN 0x37 +#define ADC_VOLUME_MAX 0xFE +#define HP_VOLUME_MAX 0x0 +#define HP_VOLUME_MIN 0x7F +#define LO_VOLUME_MAX 0x0 +#define LO_VOLUME_MIN 0x1F + +extern struct snd_soc_dai stmp378x_codec_dai; +extern struct snd_soc_codec_device soc_codec_dev_stmp378x; + +#endif /* __STMP378X_CODEC_H */ diff --git a/sound/soc/codecs/stmp3xxx_spdif.c b/sound/soc/codecs/stmp3xxx_spdif.c new file mode 100644 index 000000000000..9f51ec4ffbe4 --- /dev/null +++ b/sound/soc/codecs/stmp3xxx_spdif.c @@ -0,0 +1,412 @@ +/* + * ALSA SoC STMP3xxx SPDIF transmitter driver + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <asm/dma.h> + +#include <mach/regs-spdif.h> + +#include "stmp3xxx_spdif.h" + +#define STMP3XXX_VERSION "0.1" +struct stmp3xxx_codec_priv { + struct device *dev; + struct clk *clk; +}; + +/* + * ALSA API + */ +static u32 spdif_regmap[] = { + HW_SPDIF_CTRL_ADDR, + HW_SPDIF_STAT_ADDR, + HW_SPDIF_FRAMECTRL_ADDR, + HW_SPDIF_SRR_ADDR, + HW_SPDIF_DEBUG_ADDR, + HW_SPDIF_DATA_ADDR, + HW_SPDIF_VERSION_ADDR, +}; + +/* + * ALSA core supports only 16 bit registers. It means we have to simulate it + * by virtually splitting a 32bit SPDIF registers into two halves + * high (bits 31:16) and low (bits 15:0). The routins abow detects which part + * of 32bit register is accessed. + */ +static int stmp3xxx_codec_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + unsigned int reg_val; + unsigned int mask = 0xffff; + + if (reg >= SPDIF_REGNUM) + return -EIO; + + if (reg & 0x1) { + mask <<= 16; + value <<= 16; + } + + reg_val = __raw_readl(spdif_regmap[reg >> 1]); + reg_val = (reg_val & ~mask) | value; + __raw_writel(reg_val, spdif_regmap[reg >> 1]); + + return 0; +} + +static unsigned int stmp3xxx_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int reg_val; + + if (reg >= SPDIF_REGNUM) + return -1; + + reg_val = __raw_readl(spdif_regmap[reg >> 1]); + if (reg & 1) + reg_val >>= 16; + + return reg_val & 0xffff; +} + +/* Codec controls */ +static const struct snd_kcontrol_new stmp3xxx_snd_controls[] = { + SOC_SINGLE("PRO", SPDIF_FRAMECTRL_L, 0, 0x1, 0), + SOC_SINGLE("AUDIO", SPDIF_FRAMECTRL_L, 1, 0x1, 0), + SOC_SINGLE("COPY", SPDIF_FRAMECTRL_L, 2, 0x1, 0), + SOC_SINGLE("PRE", SPDIF_FRAMECTRL_L, 3, 0x1, 0), + SOC_SINGLE("CC", SPDIF_FRAMECTRL_L, 4, 0x7F, 0), + SOC_SINGLE("L", SPDIF_FRAMECTRL_L, 12, 0x1, 0), + SOC_SINGLE("V", SPDIF_FRAMECTRL_L, 13, 0x1, 0), + SOC_SINGLE("USER DATA", SPDIF_FRAMECTRL_L, 14, 0x1, 0), + SOC_SINGLE("AUTO MUTE", SPDIF_FRAMECTRL_H, 16, 0x1, 0), + SOC_SINGLE("V CONFIG", SPDIF_FRAMECTRL_H, 17, 0x1, 0), +}; + +/* add non dapm controls */ +static int stmp3xxx_codec_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(stmp3xxx_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&stmp3xxx_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +struct spdif_srr { + u32 rate; + u32 basemult; + u32 rate_factor; +}; + +static struct spdif_srr srr_values[] = { + {96000, 0x2, 0x0BB80}, + {88200, 0x2, 0x0AC44}, + {64000, 0x2, 0x07D00}, + {48000, 0x1, 0x0BB80}, + {44100, 0x1, 0x0AC44}, + {32000, 0x1, 0x07D00}, +}; + +static inline int get_srr_values(int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(srr_values); i++) + if (srr_values[i].rate == rate) + return i; + + return -1; +} + +static int stmp3xxx_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int i; + u32 srr_value = 0; + u32 basemult; + + i = get_srr_values(params_rate(params)); + if (i < 0) + printk(KERN_WARNING "%s doesn't support rate %d\n", + codec->name, params_rate(params)); + else { + basemult = srr_values[i].basemult; + + srr_value = BF_SPDIF_SRR_BASEMULT(basemult) | + BF_SPDIF_SRR_RATE(srr_values[i].rate_factor); + + if (playback) + HW_SPDIF_SRR_WR(srr_value); + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (playback) + HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_WORD_LENGTH); + break; + case SNDRV_PCM_FORMAT_S32_LE: + if (playback) + HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_WORD_LENGTH); + break; + default: + printk(KERN_WARNING "%s doesn't support format %d\n", + codec->name, params_format(params)); + } + + return 0; +} + +static void +stmp3xxx_codec_spdif_enable(struct stmp3xxx_codec_priv *stmp3xxx_spdif) +{ + /* Move SPDIF codec out of reset */ + HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_SFTRST); + + /* Ungate SPDIF clocks */ + HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_CLKGATE); + + /* 16 bit word length */ + HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_WORD_LENGTH); +} + +static void +stmp3xxx_codec_spdif_disable(struct stmp3xxx_codec_priv *stmp3xxx_spdif) +{ + /* Gate SPDIF clocks */ + HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_CLKGATE); +} + +static void stmp3xxx_codec_init(struct snd_soc_codec *codec) +{ + struct stmp3xxx_codec_priv *stmp3xxx_spdif = codec->private_data; + + /* Soft reset SPDIF block */ + HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_SFTRST); + while (!(HW_SPDIF_CTRL_RD() & BM_SPDIF_CTRL_CLKGATE)); + + stmp3xxx_codec_spdif_enable(stmp3xxx_spdif); + + stmp3xxx_codec_add_controls(codec); +} + +static void stmp3xxx_codec_exit(struct snd_soc_codec *codec) +{ + struct stmp3xxx_codec_priv *stmp3xxx_spdif = codec->private_data; + + stmp3xxx_codec_spdif_disable(stmp3xxx_spdif); +} + +#define STMP3XXX_SPDIF_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) +#define STMP3XXX_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai stmp3xxx_spdif_codec_dai = { + .name = "stmp3xxx spdif", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = STMP3XXX_SPDIF_RATES, + .formats = STMP3XXX_SPDIF_FORMATS, + }, + .ops = { + .hw_params = stmp3xxx_codec_hw_params, + }, +}; +EXPORT_SYMBOL_GPL(stmp3xxx_spdif_codec_dai); + +static int stmp3xxx_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct stmp3xxx_codec_priv *stmp3xxx_spdif; + int ret = 0; + + printk(KERN_INFO + "STMP3XXX SPDIF Audio Transmitter %s\n", STMP3XXX_VERSION); + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + stmp3xxx_spdif = + kzalloc(sizeof(struct stmp3xxx_codec_priv), GFP_KERNEL); + if (stmp3xxx_spdif == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->name = "stmp3xxx spdif"; + codec->owner = THIS_MODULE; + codec->private_data = stmp3xxx_spdif; + codec->read = stmp3xxx_codec_read; + codec->write = stmp3xxx_codec_write; + codec->dai = &stmp3xxx_spdif_codec_dai; + codec->num_dai = 1; + socdev->codec = codec; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "%s: failed to create pcms\n", __func__); + goto pcm_err; + } + + /* Turn on audio clock */ + stmp3xxx_spdif->dev = &pdev->dev; + stmp3xxx_spdif->clk = clk_get(stmp3xxx_spdif->dev, "spdif"); + if (IS_ERR(stmp3xxx_spdif->clk)) { + ret = PTR_ERR(stmp3xxx_spdif->clk); + printk(KERN_ERR "%s: Clocks initialization failed\n", __func__); + goto clk_err; + } + clk_enable(stmp3xxx_spdif->clk); + + stmp3xxx_codec_init(codec); + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "%s: failed to register card\n", __func__); + goto card_err; + } + + return ret; + +card_err: + clk_disable(stmp3xxx_spdif->clk); + clk_put(stmp3xxx_spdif->clk); +clk_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(socdev->codec); + return ret; +} + +static int stmp3xxx_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct stmp3xxx_codec_priv *stmp3xxx_spdif; + + if (codec == NULL) + return 0; + + stmp3xxx_spdif = codec->private_data; + + clk_disable(stmp3xxx_spdif->clk); + clk_put(stmp3xxx_spdif->clk); + + stmp3xxx_codec_exit(codec); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + kfree(socdev->codec); + + return 0; +} + +#ifdef CONFIG_PM +static int stmp3xxx_codec_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct stmp3xxx_codec_priv *stmp3xxx_spdif; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + stmp3xxx_spdif = codec->private_data; + + stmp3xxx_codec_spdif_disable(stmp3xxx_spdif); + clk_disable(stmp3xxx_spdif->clk); + ret = 0; + +out: + return ret; +} + +static int stmp3xxx_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct stmp3xxx_codec_priv *stmp3xxx_spdif; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + stmp3xxx_spdif = codec->private_data; + clk_enable(stmp3xxx_spdif->clk); + + /* Soft reset SPDIF block */ + HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_SFTRST); + while (!(HW_SPDIF_CTRL_RD() & BM_SPDIF_CTRL_CLKGATE)); + + stmp3xxx_codec_spdif_enable(stmp3xxx_spdif); + + ret = 0; + +out: + return ret; +} +#else +#define stmp3xxx_codec_suspend NULL +#define stmp3xxx_codec_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_codec_device soc_spdif_codec_dev_stmp3xxx = { + .probe = stmp3xxx_codec_probe, + .remove = stmp3xxx_codec_remove, + .suspend = stmp3xxx_codec_suspend, + .resume = stmp3xxx_codec_resume, +}; +EXPORT_SYMBOL_GPL(soc_spdif_codec_dev_stmp3xxx); + +MODULE_DESCRIPTION("STMP3XXX SPDIF transmitter"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/stmp3xxx_spdif.h b/sound/soc/codecs/stmp3xxx_spdif.h new file mode 100644 index 000000000000..0ae20a7e8cc6 --- /dev/null +++ b/sound/soc/codecs/stmp3xxx_spdif.h @@ -0,0 +1,37 @@ +/* + * ALSA SoC STMP378x SPDIF codec driver + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#ifndef __STMP3XXX_SPDIF_CODEC_H +#define __STMP3XXX_SPDIF_CODEC_H + +#define SPDIF_CTRL_L 0 +#define SPDIF_CTRL_H 1 +#define SPDIF_STAT_L 2 +#define SPDIF_STAT_H 3 +#define SPDIF_FRAMECTRL_L 4 +#define SPDIF_FRAMECTRL_H 5 +#define SPDIF_SRR_L 6 +#define SPDIF_SRR_H 7 +#define SPDIF_DEBUG_L 8 +#define SPDIF_DEBUG_H 9 +#define SPDIF_DATA_L 10 +#define SPDIF_DATA_H 11 +#define SPDIF_VERSION_L 12 +#define SPDIF_VERSION_H 13 + +#define SPDIF_REGNUM 14 + +extern struct snd_soc_dai stmp3xxx_spdif_codec_dai; +extern struct snd_soc_codec_device soc_spdif_codec_dev_stmp3xxx; + +#endif /* __STMP3XXX_SPDIF_CODEC_H */ diff --git a/sound/soc/stmp3xxx/Kconfig b/sound/soc/stmp3xxx/Kconfig new file mode 100644 index 000000000000..5a3ee6067f90 --- /dev/null +++ b/sound/soc/stmp3xxx/Kconfig @@ -0,0 +1,31 @@ +config SND_STMP3XXX_SOC + tristate "SoC Audio for the SigmaTel STMP3XXX chips" + depends on ARCH_STMP3XXX && SND_SOC + select SND_PCM + help + Say Y or M if you want to add support for codecs embedded into + the STMP3XXX chips. + +config SND_STMP3XXX_SOC_DAI + tristate + +config SND_STMP3XXX_SOC_SPDIF_DAI + tristate + +config SND_STMP3XXX_SOC_STMP3780_DEVB + tristate "SoC Audio support for STMP3780 Development Board" + depends on SND_STMP3XXX_SOC && ARCH_STMP378X + select SND_STMP3XXX_SOC_DAI + select SND_SOC_STMP378X_CODEC + help + Say Y if you want to add support for SoC audio on stmp3780 development + board with the stmp378x codec. + +config SND_STMP3XXX_SOC_STMP3780_DEVB_SPDIF + tristate "SoC SPDIF support for STMP3780 Development Board" + depends on SND_STMP3XXX_SOC && ARCH_STMP378X + select SND_STMP3XXX_SOC_SPDIF_DAI + select SND_SOC_STMP3XXX_SPDIF + help + Say Y if you want to add support for SoC audio on stmp3780 development + board with the SPDIF transmitter. diff --git a/sound/soc/stmp3xxx/Makefile b/sound/soc/stmp3xxx/Makefile new file mode 100644 index 000000000000..082e7aaf365c --- /dev/null +++ b/sound/soc/stmp3xxx/Makefile @@ -0,0 +1,15 @@ +# STMP3XXX platfrom support +snd-soc-stmp3xxx-objs := stmp3xxx_pcm.o +snd-soc-stmp3xxx-dai-objs := stmp3xxx_dai.o +snd-soc-stmp3xxx-spdif-dai-objs := stmp3xxx_spdif_dai.o + +obj-$(CONFIG_SND_STMP3XXX_SOC) += snd-soc-stmp3xxx.o +obj-$(CONFIG_SND_STMP3XXX_SOC_DAI) += snd-soc-stmp3xxx-dai.o +obj-$(CONFIG_SND_STMP3XXX_SOC_SPDIF_DAI) += snd-soc-stmp3xxx-spdif-dai.o + +# Machine Support +snd-soc-stmp3780-devb-objs := stmp3780_devb.o +snd-soc-stmp3780-devb-spdif-objs := stmp3780_devb_spdif.o + +obj-$(CONFIG_SND_STMP3XXX_SOC_STMP3780_DEVB) += snd-soc-stmp3780-devb.o +obj-$(CONFIG_SND_STMP3XXX_SOC_STMP3780_DEVB_SPDIF) += snd-soc-stmp3780-devb-spdif.o diff --git a/sound/soc/stmp3xxx/stmp3780_devb.c b/sound/soc/stmp3xxx/stmp3780_devb.c new file mode 100644 index 000000000000..acb6414cc52f --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3780_devb.c @@ -0,0 +1,97 @@ +/* + * ASoC driver for Freescale STMP3780 development board + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <asm/dma.h> +#include <mach/hardware.h> +#include <mach/regs-apbx.h> + +#include "../codecs/stmp378x_codec.h" +#include "stmp3xxx_dai.h" +#include "stmp3xxx_pcm.h" + +/* stmp3780 devb digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link stmp3780_devb_dai = { + .name = "STMP378X ADC/DAC", + .stream_name = "STMP378X ADC/DAC", + .cpu_dai = &stmp3xxx_adc_dai, + .codec_dai = &stmp378x_codec_dai, +}; + +/* stmp3780 devb audio machine driver */ +static struct snd_soc_machine snd_soc_machine_stmp3780_devb = { + .name = "STMP3780 Devb", + .dai_link = &stmp3780_devb_dai, + .num_links = 1, +}; + +/* stmp3780 devb audio subsystem */ +static struct snd_soc_device stmp3780_devb_snd_devdata = { + .machine = &snd_soc_machine_stmp3780_devb, + .platform = &stmp3xxx_soc_platform, + .codec_dev = &soc_codec_dev_stmp378x, +}; + +static struct platform_device *stmp3780_devb_snd_device; + +static int __init stmp3780_devb_init(void) +{ + int ret = 0; + + stmp3780_devb_snd_device = platform_device_alloc("soc-audio", 0); + if (!stmp3780_devb_snd_device) + return -ENOMEM; + + platform_set_drvdata(stmp3780_devb_snd_device, + &stmp3780_devb_snd_devdata); + stmp3780_devb_snd_devdata.dev = &stmp3780_devb_snd_device->dev; + stmp3780_devb_snd_device->dev.platform_data = + &stmp3780_devb_snd_devdata; + + if (ret) { + platform_device_put(stmp3780_devb_snd_device); + return ret; + } + + ret = platform_device_add(stmp3780_devb_snd_device); + if (ret) + platform_device_put(stmp3780_devb_snd_device); + + return ret; +} + +static void __exit stmp3780_devb_exit(void) +{ + platform_device_unregister(stmp3780_devb_snd_device); +} + +module_init(stmp3780_devb_init); +module_exit(stmp3780_devb_exit); + +MODULE_AUTHOR("Vladislav Buzov"); +MODULE_DESCRIPTION("STMP3780 development board ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/stmp3xxx/stmp3780_devb_spdif.c b/sound/soc/stmp3xxx/stmp3780_devb_spdif.c new file mode 100644 index 000000000000..c8dbe2cf6958 --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3780_devb_spdif.c @@ -0,0 +1,95 @@ +/* + * ASoC driver for STMP3780 development board + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <asm/dma.h> +#include <mach/hardware.h> +#include <mach/regs-apbx.h> + +#include <mach/stmp3xxx.h> + +#include "../codecs/stmp3xxx_spdif.h" +#include "stmp3xxx_spdif_dai.h" +#include "stmp3xxx_pcm.h" + +/* stmp3780 devb digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link stmp3780_devb_dai = { + .name = "STMP3XXX SPDIF", + .stream_name = "STMP3XXX SPDIF", + .cpu_dai = &stmp3xxx_spdif_dai, + .codec_dai = &stmp3xxx_spdif_codec_dai, +}; + +/* stmp3780 devb audio machine driver */ +static struct snd_soc_machine snd_soc_machine_stmp3780_devb = { + .name = "STMP3780 Devb", + .dai_link = &stmp3780_devb_dai, + .num_links = 1, +}; + +/* stmp3780 devb audio subsystem */ +static struct snd_soc_device stmp3780_devb_snd_devdata = { + .machine = &snd_soc_machine_stmp3780_devb, + .platform = &stmp3xxx_soc_platform, + .codec_dev = &soc_spdif_codec_dev_stmp3xxx, +}; + +static struct platform_device *stmp3780_devb_snd_device; + +static int __init stmp3780_devb_init(void) +{ + int ret = 0; + + stmp3780_devb_snd_device = platform_device_alloc("soc-audio", 1); + if (!stmp3780_devb_snd_device) + return -ENOMEM; + + platform_set_drvdata(stmp3780_devb_snd_device, + &stmp3780_devb_snd_devdata); + stmp3780_devb_snd_devdata.dev = &stmp3780_devb_snd_device->dev; + stmp3780_devb_snd_device->dev.platform_data = + &stmp3780_devb_snd_devdata; + + ret = platform_device_add(stmp3780_devb_snd_device); + if (ret) + goto out; + + ret = spdif_pinmux_request(); +out: + if (ret) + platform_device_put(stmp3780_devb_snd_device); + return ret; +} + +static void __exit stmp3780_devb_exit(void) +{ + spdif_pinmux_release(); + platform_device_unregister(stmp3780_devb_snd_device); +} + +module_init(stmp3780_devb_init); +module_exit(stmp3780_devb_exit); + +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_DESCRIPTION("STMP3780 development board ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/stmp3xxx/stmp3xxx_dai.c b/sound/soc/stmp3xxx/stmp3xxx_dai.c new file mode 100644 index 000000000000..0fa7e1d89bf1 --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3xxx_dai.c @@ -0,0 +1,224 @@ +/* + * ASoC Audio Layer for Freescale STMP37XX/STMP378X ADC/DAC + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <asm/dma.h> +#include <mach/regs-audioin.h> +#include <mach/regs-audioout.h> +#include "stmp3xxx_pcm.h" + +#define STMP3XXX_ADC_RATES SNDRV_PCM_RATE_8000_192000 +#define STMP3XXX_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct stmp3xxx_pcm_dma_params stmp3xxx_audio_in = { + .name = "stmp3xxx adc", + .dma_bus = STMP3XXX_BUS_APBX, + .dma_ch = 0, + .irq = IRQ_ADC_DMA, +}; + +struct stmp3xxx_pcm_dma_params stmp3xxx_audio_out = { + .name = "stmp3xxx dac", + .dma_bus = STMP3XXX_BUS_APBX, + .dma_ch = 1, + .irq = IRQ_DAC_DMA, +}; + +static irqreturn_t stmp3xxx_err_irq(int irq, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + u32 ctrl_reg; + u32 overflow_mask; + u32 underflow_mask; + + if (playback) { + ctrl_reg = HW_AUDIOOUT_CTRL_RD(); + underflow_mask = BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ; + overflow_mask = BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ; + } else { + ctrl_reg = HW_AUDIOIN_CTRL_RD(); + underflow_mask = BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ; + overflow_mask = BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ; + } + + if (ctrl_reg & underflow_mask) { + printk(KERN_DEBUG "%s underflow detected\n", + playback ? "DAC" : "ADC"); + + if (playback) + HW_AUDIOOUT_CTRL_CLR( + BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ); + else + HW_AUDIOIN_CTRL_CLR( + BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ); + + } else if (ctrl_reg & overflow_mask) { + printk(KERN_DEBUG "%s overflow detected\n", + playback ? "DAC" : "ADC"); + + if (playback) + HW_AUDIOOUT_CTRL_CLR( + BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ); + else + HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ); + } else + printk(KERN_WARNING "Unknown DAC error interrupt\n"); + + return IRQ_HANDLED; +} + +static int stmp3xxx_adc_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (playback) + HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_RUN); + else + HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_RUN); + break; + + case SNDRV_PCM_TRIGGER_STOP: + if (playback) + HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_RUN); + else + HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_RUN); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int stmp3xxx_adc_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int irq; + int ret; + + if (playback) { + irq = IRQ_DAC_ERROR; + cpu_dai->dma_data = &stmp3xxx_audio_out; + } else { + irq = IRQ_ADC_ERROR; + cpu_dai->dma_data = &stmp3xxx_audio_in; + } + + ret = request_irq(irq, stmp3xxx_err_irq, 0, "STMP3xxx DAC/ADC Error", + substream); + if (ret) { + printk(KERN_ERR "%s: Unable to request ADC/DAC error irq %d\n", + __func__, IRQ_DAC_ERROR); + return ret; + } + + /* Enable error interrupt */ + if (playback) { + HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ); + HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ); + HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN); + } else { + HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ); + HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ); + HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_FIFO_ERROR_IRQ_EN); + } + + return 0; +} + +static void stmp3xxx_adc_shutdown(struct snd_pcm_substream *substream) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + + /* Disable error interrupt */ + if (playback) { + HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN); + free_irq(IRQ_DAC_ERROR, substream); + } else { + HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_FIFO_ERROR_IRQ_EN); + free_irq(IRQ_ADC_ERROR, substream); + } +} + +#ifdef CONFIG_PM +static int stmp3xxx_adc_suspend(struct platform_device *pdev, + struct snd_soc_dai *cpu_dai) +{ + return 0; +} + +static int stmp3xxx_adc_resume(struct platform_device *pdev, + struct snd_soc_dai *cpu_dai) +{ + return 0; +} +#else +#define stmp3xxx_adc_suspend NULL +#define stmp3xxx_adc_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_dai stmp3xxx_adc_dai = { + .name = "stmp3xxx adc/dac", + .id = 0, + .type = SND_SOC_DAI_PCM, + .suspend = stmp3xxx_adc_suspend, + .resume = stmp3xxx_adc_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = STMP3XXX_ADC_RATES, + .formats = STMP3XXX_ADC_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = STMP3XXX_ADC_RATES, + .formats = STMP3XXX_ADC_FORMATS, + }, + .ops = { + .startup = stmp3xxx_adc_startup, + .shutdown = stmp3xxx_adc_shutdown, + .trigger = stmp3xxx_adc_trigger, + }, +}; +EXPORT_SYMBOL_GPL(stmp3xxx_adc_dai); + +MODULE_AUTHOR("Vladislav Buzov"); +MODULE_DESCRIPTION("stmp3xxx dac/adc DAI"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/stmp3xxx/stmp3xxx_dai.h b/sound/soc/stmp3xxx/stmp3xxx_dai.h new file mode 100644 index 000000000000..409256a86d15 --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3xxx_dai.h @@ -0,0 +1,21 @@ +/* + * ASoC Audio Layer for Freescale STMP37XX/STMP378X ADC/DAC + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef _STMP3XXX_DEV_H +#define _STMP3XXX_DEV_H +extern struct snd_soc_dai stmp3xxx_adc_dai; +#endif diff --git a/sound/soc/stmp3xxx/stmp3xxx_pcm.c b/sound/soc/stmp3xxx/stmp3xxx_pcm.c new file mode 100644 index 000000000000..79fd5526fe6b --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3xxx_pcm.c @@ -0,0 +1,440 @@ +/* + * ASoC PCM interface for Freescale STMP37XX/STMP378X ADC/DAC + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/dma.h> +#include <mach/hardware.h> + +#include <mach/regs-apbx.h> + +#include "stmp3xxx_pcm.h" + +static const struct snd_pcm_hardware stmp3xxx_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 255, + .buffer_bytes_max = 64 * 1024, + .fifo_size = 32, +}; + +/* + * Required to request DMA channels + */ +struct device *stmp3xxx_pcm_dev; + +struct stmp3xxx_runtime_data { + u32 dma_ch; + u32 dma_period; + u32 dma_totsize; + + struct stmp3xxx_pcm_dma_params *params; + struct stmp3xxx_dma_descriptor *dma_desc_array; +}; + +static irqreturn_t stmp3xxx_pcm_dma_irq(int irq, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + struct stmp3xxx_runtime_data *prtd = substream->runtime->private_data; + +#ifdef CONFIG_ARCH_STMP37XX + u32 err_mask = 1 << (16 + prtd->params->dma_ch); +#endif +#ifdef CONFIG_ARCH_STMP378X + u32 err_mask = 1 << prtd->params->dma_ch; +#endif + u32 irq_mask = 1 << prtd->params->dma_ch; + +#ifdef CONFIG_ARCH_STMP37XX + if (HW_APBX_CTRL1_RD() & err_mask) { +#endif +#ifdef CONFIG_ARCH_STMP378X + if (HW_APBX_CTRL2_RD() & err_mask) { +#endif + printk(KERN_WARNING "%s: DMA audio channel %d (%s) error\n", + __func__, prtd->params->dma_ch, prtd->params->name); +#ifdef CONFIG_ARCH_STMP37XX + HW_APBX_CTRL1_CLR(err_mask); +#endif +#ifdef CONFIG_ARCH_STMP378X + HW_APBX_CTRL2_CLR(err_mask); +#endif + } else if (HW_APBX_CTRL1_RD() & irq_mask) { + stmp3xxx_dma_clear_interrupt(prtd->dma_ch); + snd_pcm_period_elapsed(substream); + } else + printk(KERN_WARNING "%s: Unknown interrupt\n", __func__); + + return IRQ_HANDLED; +} + +/* + * Make a circular DMA descriptor list + */ +static int stmp3xxx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stmp3xxx_runtime_data *prtd = runtime->private_data; + dma_addr_t dma_buffer_phys; + int periods_num, playback, i; + + playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + periods_num = prtd->dma_totsize / prtd->dma_period; + dma_buffer_phys = runtime->dma_addr; + + /* Reset DMA channel, enable interrupt */ + stmp3xxx_dma_reset_channel(prtd->dma_ch); + + /* Set up a DMA chain to sent DMA buffer */ + for (i = 0; i < periods_num; i++) { + int next = (i + 1) % periods_num; + u32 cmd = 0; + + /* Link with previous command */ + prtd->dma_desc_array[i].command->next = + prtd->dma_desc_array[next].handle; + + prtd->dma_desc_array[i].next_descr = + &prtd->dma_desc_array[next]; + + cmd = BF_APBX_CHn_CMD_XFER_COUNT(prtd->dma_period) | + BM_APBX_CHn_CMD_IRQONCMPLT | + BM_APBX_CHn_CMD_CHAIN; + + /* Set DMA direction */ + if (playback) + cmd |= BF_APBX_CHn_CMD_COMMAND( + BV_APBX_CHn_CMD_COMMAND__DMA_READ); + else + cmd |= BF_APBX_CHn_CMD_COMMAND( + BV_APBX_CHn_CMD_COMMAND__DMA_WRITE); + + prtd->dma_desc_array[i].command->cmd = cmd; + prtd->dma_desc_array[i].command->buf_ptr = dma_buffer_phys; + + /* Next data chunk */ + dma_buffer_phys += prtd->dma_period; + } + + return 0; +} + +/* + * Stop circular DMA descriptor list + * We should not stop DMA in a middle of current transaction once we receive + * stop request from ALSA core. This function finds the next DMA descriptor + * and set it up to decrement DMA channel semaphore. So the current transaction + * is the last data transfer. + */ +static void stmp3xxx_pcm_stop(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stmp3xxx_runtime_data *prtd = runtime->private_data; + dma_addr_t pos; + int desc; + + /* Freez DMA channel for a moment */ + stmp3xxx_dma_freeze(prtd->dma_ch); + + /* Find current DMA descriptor */ + pos = HW_APBX_CHn_BAR_RD(prtd->params->dma_ch); + desc = (pos - runtime->dma_addr) / prtd->dma_period; + + /* Set up the next descriptor to decrement DMA channel sempahore */ + prtd->dma_desc_array[desc].next_descr->command->cmd + = BM_APBX_CHn_CMD_SEMAPHORE; + + /* Let the current DMA transaction finish */ + stmp3xxx_dma_unfreeze(prtd->dma_ch); +} + +static int stmp3xxx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct stmp3xxx_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + + switch (cmd) { + + case SNDRV_PCM_TRIGGER_START: + stmp3xxx_dma_go(prtd->dma_ch, prtd->dma_desc_array, 1); + break; + + case SNDRV_PCM_TRIGGER_STOP: + stmp3xxx_pcm_stop(substream); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + stmp3xxx_dma_unfreeze(prtd->dma_ch); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + stmp3xxx_dma_freeze(prtd->dma_ch); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t +stmp3xxx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stmp3xxx_runtime_data *prtd = runtime->private_data; + unsigned int offset; + dma_addr_t pos; + + pos = HW_APBX_CHn_BAR_RD(prtd->params->dma_ch); + offset = bytes_to_frames(runtime, pos - runtime->dma_addr); + + if (offset >= runtime->buffer_size) + offset = 0; + + return offset; +} + +static int stmp3xxx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct stmp3xxx_runtime_data *prtd = substream->runtime->private_data; + + prtd->dma_period = params_period_bytes(hw_params); + prtd->dma_totsize = params_buffer_bytes(hw_params); + + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int stmp3xxx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int stmp3xxx_pcm_dma_request(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct stmp3xxx_runtime_data *prtd = runtime->private_data; + struct stmp3xxx_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data; + int desc_num = stmp3xxx_pcm_hardware.periods_max; + int desc; + int ret; + + if (!dma_data) + return -ENODEV; + + prtd->params = dma_data; + prtd->dma_ch = STMP3xxx_DMA(dma_data->dma_ch, dma_data->dma_bus); + + ret = stmp3xxx_dma_request(prtd->dma_ch, stmp3xxx_pcm_dev, + prtd->params->name); + if (ret) { + printk(KERN_ERR "%s: Failed to request DMA channel (%d:%d)\n", + __func__, dma_data->dma_bus, dma_data->dma_ch); + return ret; + } + + /* Allocate memory for data and pio DMA descriptors */ + prtd->dma_desc_array = + kzalloc(sizeof(struct stmp3xxx_dma_descriptor) * desc_num, + GFP_KERNEL); + if (prtd->dma_desc_array == NULL) { + printk(KERN_ERR "%s: Unable to allocate memory\n", __func__); + stmp3xxx_dma_release(prtd->dma_ch); + return -ENOMEM; + } + + for (desc = 0; desc < desc_num; desc++) { + ret = stmp3xxx_dma_allocate_command(prtd->dma_ch, + &prtd->dma_desc_array[desc]); + if (ret) { + printk(KERN_ERR"%s Unable to allocate DMA command %d\n", + __func__, desc); + goto err; + } + } + + ret = request_irq(prtd->params->irq, stmp3xxx_pcm_dma_irq, 0, + "STMP3xxx PCM DMA", substream); + if (ret) { + printk(KERN_ERR "%s: Unable to request DMA irq %d\n", __func__, + prtd->params->irq); + goto err; + } + + + /* Enable completion interrupt */ + stmp3xxx_dma_clear_interrupt(prtd->dma_ch); + stmp3xxx_dma_enable_interrupt(prtd->dma_ch); + + return 0; + +err: + while (--desc >= 0) + stmp3xxx_dma_free_command(prtd->dma_ch, + &prtd->dma_desc_array[desc]); + kfree(prtd->dma_desc_array); + stmp3xxx_dma_release(prtd->dma_ch); + + return ret; +} + +static int stmp3xxx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stmp3xxx_runtime_data *prtd; + int ret; + + snd_soc_set_runtime_hwparams(substream, &stmp3xxx_pcm_hardware); + + prtd = kzalloc(sizeof(struct stmp3xxx_runtime_data), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + runtime->private_data = prtd; + + ret = stmp3xxx_pcm_dma_request(substream); + if (ret) { + printk(KERN_ERR "stmp3xxx_pcm: Failed to request channels\n"); + kfree(prtd); + return ret; + } + + return 0; +} + +static int stmp3xxx_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stmp3xxx_runtime_data *prtd = runtime->private_data; + int desc_num = stmp3xxx_pcm_hardware.periods_max; + int desc; + + /* Free DMA irq */ + free_irq(prtd->params->irq, substream); + + /* Free DMA channel */ + for (desc = 0; desc < desc_num; desc++) + stmp3xxx_dma_free_command(prtd->dma_ch, + &prtd->dma_desc_array[desc]); + kfree(prtd->dma_desc_array); + stmp3xxx_dma_release(prtd->dma_ch); + + /* Free private runtime data */ + kfree(prtd); + + return 0; +} + +static int stmp3xxx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_coherent(NULL, vma, runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); +} + +struct snd_pcm_ops stmp3xxx_pcm_ops = { + .open = stmp3xxx_pcm_open, + .close = stmp3xxx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = stmp3xxx_pcm_hw_params, + .hw_free = stmp3xxx_pcm_hw_free, + .prepare = stmp3xxx_pcm_prepare, + .trigger = stmp3xxx_pcm_trigger, + .pointer = stmp3xxx_pcm_pointer, + .mmap = stmp3xxx_pcm_mmap, +}; + +static u64 stmp3xxx_pcm_dma_mask = DMA_32BIT_MASK; + +static int stmp3xxx_pcm_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + size_t size = stmp3xxx_pcm_hardware.buffer_bytes_max; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &stmp3xxx_pcm_dma_mask; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_32BIT_MASK; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, NULL, + size, size); + + return 0; +} + +static void stmp3xxx_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +/* + * We need probe/remove callbacks to setup stmp3xxx_pcm_dev + */ +static int stmp3xxx_pcm_probe(struct platform_device *pdev) +{ + stmp3xxx_pcm_dev = &pdev->dev; + return 0; +} + +static int stmp3xxx_pcm_remove(struct platform_device *pdev) +{ + stmp3xxx_pcm_dev = NULL; + return 0; +} + +struct snd_soc_platform stmp3xxx_soc_platform = { + .name = "STMP3xxx Audio", + .pcm_ops = &stmp3xxx_pcm_ops, + .probe = stmp3xxx_pcm_probe, + .remove = stmp3xxx_pcm_remove, + .pcm_new = stmp3xxx_pcm_new, + .pcm_free = stmp3xxx_pcm_free, +}; +EXPORT_SYMBOL_GPL(stmp3xxx_soc_platform); + +MODULE_AUTHOR("Vladislav Buzov"); +MODULE_DESCRIPTION("STMP3xxx DMA Module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/stmp3xxx/stmp3xxx_pcm.h b/sound/soc/stmp3xxx/stmp3xxx_pcm.h new file mode 100644 index 000000000000..78ac9487353f --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3xxx_pcm.h @@ -0,0 +1,30 @@ +/* + * ASoC PCM interface for Freescale STMP37XX/STMP378X ADC/DAC + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef _STMP3XXX_PCM_H +#define _STMP3XXX_PCM_H + +struct stmp3xxx_pcm_dma_params { + char *name; + int dma_bus; /* DMA bus */ + int dma_ch; /* DMA channel number */ + int irq; /* DMA interrupt number */ +}; + +extern struct snd_soc_platform stmp3xxx_soc_platform; + +#endif diff --git a/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c new file mode 100644 index 000000000000..4529b3fcaf42 --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c @@ -0,0 +1,184 @@ +/* + * ALSA SoC SPDIF Audio Layer for STMP3xxx processor familiy + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <asm/dma.h> +#include <mach/regs-spdif.h> +#include "stmp3xxx_pcm.h" + +#define STMP3XXX_SPDIF_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) +#define STMP3XXX_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct stmp3xxx_pcm_dma_params stmp3xxx_spdif = { + .name = "stmp3xxx spdif", + .dma_bus = STMP3XXX_BUS_APBX, + .dma_ch = 2, + .irq = IRQ_SPDIF_DMA, +}; + +static irqreturn_t stmp3xxx_err_irq(int irq, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + u32 ctrl_reg = 0; + u32 overflow_mask; + u32 underflow_mask; + + if (playback) { + ctrl_reg = HW_SPDIF_CTRL_RD(); + underflow_mask = BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ; + overflow_mask = BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ; + } + + if (ctrl_reg & underflow_mask) { + printk(KERN_DEBUG "underflow detected SPDIF\n"); + + if (playback) + HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ); + } else if (ctrl_reg & overflow_mask) { + printk(KERN_DEBUG "overflow detected SPDIF\n"); + + if (playback) + HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ); + } else + printk(KERN_WARNING "Unknown SPDIF error interrupt\n"); + + return IRQ_HANDLED; +} + +static int stmp3xxx_spdif_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (playback) + HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_RUN); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (playback) + HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_RUN); + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + + default: + ret = -EINVAL; + } + + return ret; +} + +static int stmp3xxx_spdif_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int irq; + int ret; + + if (playback) { + irq = IRQ_SPDIF_ERROR; + cpu_dai->dma_data = &stmp3xxx_spdif; + } + + ret = request_irq(irq, stmp3xxx_err_irq, 0, "STMP3xxx SPDIF Error", + substream); + if (ret) { + printk(KERN_ERR "%s: Unable to request SPDIF error irq %d\n", + __func__, IRQ_SPDIF_ERROR); + return ret; + } + + /* Enable error interrupt */ + if (playback) { + HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ); + HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ); + HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN); + } + + return 0; +} + +static void stmp3xxx_spdif_shutdown(struct snd_pcm_substream *substream) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + + /* Disable error interrupt */ + if (playback) { + HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN); + free_irq(IRQ_SPDIF_ERROR, substream); + } +} + +#ifdef CONFIG_PM +static int stmp3xxx_spdif_suspend(struct platform_device *pdev, + struct snd_soc_dai *cpu_dai) +{ + return 0; +} + +static int stmp3xxx_spdif_resume(struct platform_device *pdev, + struct snd_soc_dai *cpu_dai) +{ + return 0; +} +#else +#define stmp3xxx_spdif_suspend NULL +#define stmp3xxx_spdif_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_dai stmp3xxx_spdif_dai = { + .name = "stmp3xxx spdif", + .id = 0, + .type = SND_SOC_DAI_PCM, + .suspend = stmp3xxx_spdif_suspend, + .resume = stmp3xxx_spdif_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = STMP3XXX_SPDIF_RATES, + .formats = STMP3XXX_SPDIF_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = STMP3XXX_SPDIF_RATES, + .formats = STMP3XXX_SPDIF_FORMATS, + }, + .ops = { + .startup = stmp3xxx_spdif_startup, + .shutdown = stmp3xxx_spdif_shutdown, + .trigger = stmp3xxx_spdif_trigger, + }, +}; +EXPORT_SYMBOL_GPL(stmp3xxx_spdif_dai); + +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_DESCRIPTION("stmp3xxx SPDIF DAI"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h new file mode 100644 index 000000000000..bec3d6fad7c9 --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h @@ -0,0 +1,21 @@ +/* + * ASoC Audio Layer for Freescale STMP3XXX SPDIF transmitter + * + * Author: Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef _STMP3XXX_SPDIF_H +#define _STMP3XXX_SPDIF_H +extern struct snd_soc_dai stmp3xxx_spdif_dai; +#endif |