diff options
-rw-r--r-- | Documentation/sound/alsa/soc/jack.txt | 71 | ||||
-rw-r--r-- | sound/arm/pxa2xx-ac97-lib.c | 15 | ||||
-rw-r--r-- | sound/soc/codecs/twl4030.c | 59 | ||||
-rw-r--r-- | sound/soc/codecs/twl4030.h | 1 | ||||
-rw-r--r-- | sound/soc/codecs/wm9705.c | 37 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_dma.c | 17 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_ssi.c | 99 | ||||
-rw-r--r-- | sound/soc/omap/omap-mcbsp.c | 11 | ||||
-rw-r--r-- | sound/soc/pxa/Kconfig | 10 | ||||
-rw-r--r-- | sound/soc/pxa/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/pxa/magician.c | 560 | ||||
-rw-r--r-- | sound/soc/pxa/pxa-ssp.c | 12 | ||||
-rw-r--r-- | sound/soc/soc-core.c | 20 |
13 files changed, 850 insertions, 64 deletions
diff --git a/Documentation/sound/alsa/soc/jack.txt b/Documentation/sound/alsa/soc/jack.txt new file mode 100644 index 00000000000..fcf82a41729 --- /dev/null +++ b/Documentation/sound/alsa/soc/jack.txt @@ -0,0 +1,71 @@ +ASoC jack detection +=================== + +ALSA has a standard API for representing physical jacks to user space, +the kernel side of which can be seen in include/sound/jack.h. ASoC +provides a version of this API adding two additional features: + + - It allows more than one jack detection method to work together on one + user visible jack. In embedded systems it is common for multiple + to be present on a single jack but handled by separate bits of + hardware. + + - Integration with DAPM, allowing DAPM endpoints to be updated + automatically based on the detected jack status (eg, turning off the + headphone outputs if no headphones are present). + +This is done by splitting the jacks up into three things working +together: the jack itself represented by a struct snd_soc_jack, sets of +snd_soc_jack_pins representing DAPM endpoints to update and blocks of +code providing jack reporting mechanisms. + +For example, a system may have a stereo headset jack with two reporting +mechanisms, one for the headphone and one for the microphone. Some +systems won't be able to use their speaker output while a headphone is +connected and so will want to make sure to update both speaker and +headphone when the headphone jack status changes. + +The jack - struct snd_soc_jack +============================== + +This represents a physical jack on the system and is what is visible to +user space. The jack itself is completely passive, it is set up by the +machine driver and updated by jack detection methods. + +Jacks are created by the machine driver calling snd_soc_jack_new(). + +snd_soc_jack_pin +================ + +These represent a DAPM pin to update depending on some of the status +bits supported by the jack. Each snd_soc_jack has zero or more of these +which are updated automatically. They are created by the machine driver +and associated with the jack using snd_soc_jack_add_pins(). The status +of the endpoint may configured to be the opposite of the jack status if +required (eg, enabling a built in microphone if a microphone is not +connected via a jack). + +Jack detection methods +====================== + +Actual jack detection is done by code which is able to monitor some +input to the system and update a jack by calling snd_soc_jack_report(), +specifying a subset of bits to update. The jack detection code should +be set up by the machine driver, taking configuration for the jack to +update and the set of things to report when the jack is connected. + +Often this is done based on the status of a GPIO - a handler for this is +provided by the snd_soc_jack_add_gpio() function. Other methods are +also available, for example integrated into CODECs. One example of +CODEC integrated jack detection can be see in the WM8350 driver. + +Each jack may have multiple reporting mechanisms, though it will need at +least one to be useful. + +Machine drivers +=============== + +These are all hooked together by the machine driver depending on the +system hardware. The machine driver will set up the snd_soc_jack and +the list of pins to update then set up one or more jack detection +mechanisms to update that jack based on their current status. diff --git a/sound/arm/pxa2xx-ac97-lib.c b/sound/arm/pxa2xx-ac97-lib.c index 7793d2a511c..0afd1a8226f 100644 --- a/sound/arm/pxa2xx-ac97-lib.c +++ b/sound/arm/pxa2xx-ac97-lib.c @@ -238,6 +238,8 @@ static inline void pxa_ac97_cold_pxa3xx(void) bool pxa2xx_ac97_try_warm_reset(struct snd_ac97 *ac97) { + unsigned long gsr; + #ifdef CONFIG_PXA25x if (cpu_is_pxa25x()) pxa_ac97_warm_pxa25x(); @@ -254,10 +256,10 @@ bool pxa2xx_ac97_try_warm_reset(struct snd_ac97 *ac97) else #endif BUG(); - - if (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR))) { + gsr = GSR | gsr_bits; + if (!(gsr & (GSR_PCR | GSR_SCR))) { printk(KERN_INFO "%s: warm reset timeout (GSR=%#lx)\n", - __func__, gsr_bits); + __func__, gsr); return false; } @@ -268,6 +270,8 @@ EXPORT_SYMBOL_GPL(pxa2xx_ac97_try_warm_reset); bool pxa2xx_ac97_try_cold_reset(struct snd_ac97 *ac97) { + unsigned long gsr; + #ifdef CONFIG_PXA25x if (cpu_is_pxa25x()) pxa_ac97_cold_pxa25x(); @@ -285,9 +289,10 @@ bool pxa2xx_ac97_try_cold_reset(struct snd_ac97 *ac97) #endif BUG(); - if (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR))) { + gsr = GSR | gsr_bits; + if (!(gsr & (GSR_PCR | GSR_SCR))) { printk(KERN_INFO "%s: cold reset timeout (GSR=%#lx)\n", - __func__, gsr_bits); + __func__, gsr); return false; } diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 97738e2ece0..bfda7a88e82 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -122,6 +122,9 @@ struct twl4030_priv { unsigned int bypass_state; unsigned int codec_powered; unsigned int codec_muted; + + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; }; /* @@ -1217,6 +1220,50 @@ static int twl4030_set_bias_level(struct snd_soc_codec *codec, return 0; } +static int twl4030_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct twl4030_priv *twl4030 = codec->private_data; + + /* If we already have a playback or capture going then constrain + * this substream to match it. + */ + if (twl4030->master_substream) { + struct snd_pcm_runtime *master_runtime; + master_runtime = twl4030->master_substream->runtime; + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + master_runtime->rate, + master_runtime->rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + master_runtime->sample_bits, + master_runtime->sample_bits); + + twl4030->slave_substream = substream; + } else + twl4030->master_substream = substream; + + return 0; +} + +static void twl4030_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct twl4030_priv *twl4030 = codec->private_data; + + if (twl4030->master_substream == substream) + twl4030->master_substream = twl4030->slave_substream; + + twl4030->slave_substream = NULL; +} + static int twl4030_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -1224,8 +1271,13 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; + struct twl4030_priv *twl4030 = codec->private_data; u8 mode, old_mode, format, old_format; + if (substream == twl4030->slave_substream) + /* Ignoring hw_params for slave substream */ + return 0; + /* bit rate */ old_mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) & ~TWL4030_CODECPDZ; @@ -1259,6 +1311,9 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, case 48000: mode |= TWL4030_APLL_RATE_48000; break; + case 96000: + mode |= TWL4030_APLL_RATE_96000; + break; default: printk(KERN_ERR "TWL4030 hw params: unknown rate %d\n", params_rate(params)); @@ -1384,6 +1439,8 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, #define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE) static struct snd_soc_dai_ops twl4030_dai_ops = { + .startup = twl4030_startup, + .shutdown = twl4030_shutdown, .hw_params = twl4030_hw_params, .set_sysclk = twl4030_set_dai_sysclk, .set_fmt = twl4030_set_dai_fmt, @@ -1395,7 +1452,7 @@ struct snd_soc_dai twl4030_dai = { .stream_name = "Playback", .channels_min = 2, .channels_max = 2, - .rates = TWL4030_RATES, + .rates = TWL4030_RATES | SNDRV_PCM_RATE_96000, .formats = TWL4030_FORMATS,}, .capture = { .stream_name = "Capture", diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h index 33dbb144dad..cb63765db1d 100644 --- a/sound/soc/codecs/twl4030.h +++ b/sound/soc/codecs/twl4030.h @@ -109,6 +109,7 @@ #define TWL4030_APLL_RATE_32000 0x80 #define TWL4030_APLL_RATE_44100 0x90 #define TWL4030_APLL_RATE_48000 0xA0 +#define TWL4030_APLL_RATE_96000 0xE0 #define TWL4030_SEL_16K 0x04 #define TWL4030_CODECPDZ 0x02 #define TWL4030_OPT_MODE 0x01 diff --git a/sound/soc/codecs/wm9705.c b/sound/soc/codecs/wm9705.c index 3265817c5c2..6e23a81dba7 100644 --- a/sound/soc/codecs/wm9705.c +++ b/sound/soc/codecs/wm9705.c @@ -317,6 +317,41 @@ static int wm9705_reset(struct snd_soc_codec *codec) return -EIO; } +#ifdef CONFIG_PM +static int wm9705_soc_suspend(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + soc_ac97_ops.write(codec->ac97, AC97_POWERDOWN, 0xffff); + + return 0; +} + +static int wm9705_soc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int i, ret; + u16 *cache = codec->reg_cache; + + ret = wm9705_reset(codec); + if (ret < 0) { + printk(KERN_ERR "could not reset AC97 codec\n"); + return ret; + } + + for (i = 2; i < ARRAY_SIZE(wm9705_reg) << 1; i += 2) { + soc_ac97_ops.write(codec->ac97, i, cache[i>>1]); + } + + return 0; +} +#else +#define wm9705_soc_suspend NULL +#define wm9705_soc_resume NULL +#endif + static int wm9705_soc_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -407,6 +442,8 @@ static int wm9705_soc_remove(struct platform_device *pdev) struct snd_soc_codec_device soc_codec_dev_wm9705 = { .probe = wm9705_soc_probe, .remove = wm9705_soc_remove, + .suspend = wm9705_soc_suspend, + .resume = wm9705_soc_resume, }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm9705); diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c index b3eb8570cd7..2c4892c853c 100644 --- a/sound/soc/fsl/fsl_dma.c +++ b/sound/soc/fsl/fsl_dma.c @@ -697,6 +697,23 @@ static snd_pcm_uframes_t fsl_dma_pointer(struct snd_pcm_substream *substream) else position = in_be32(&dma_channel->dar); + /* + * When capture is started, the SSI immediately starts to fill its FIFO. + * This means that the DMA controller is not started until the FIFO is + * full. However, ALSA calls this function before that happens, when + * MR.DAR is still zero. In this case, just return zero to indicate + * that nothing has been received yet. + */ + if (!position) + return 0; + + if ((position < dma_private->dma_buf_phys) || + (position > dma_private->dma_buf_end)) { + dev_err(substream->pcm->card->dev, + "dma pointer is out of range, halting stream\n"); + return SNDRV_PCM_POS_XRUN; + } + frames = bytes_to_frames(runtime, position - dma_private->dma_buf_phys); /* diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 169bca295b7..3711d8454d9 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -60,6 +60,13 @@ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE) #endif +/* SIER bitflag of interrupts to enable */ +#define SIER_FLAGS (CCSR_SSI_SIER_TFRC_EN | CCSR_SSI_SIER_TDMAE | \ + CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TUE0_EN | \ + CCSR_SSI_SIER_TUE1_EN | CCSR_SSI_SIER_RFRC_EN | \ + CCSR_SSI_SIER_RDMAE | CCSR_SSI_SIER_RIE | \ + CCSR_SSI_SIER_ROE0_EN | CCSR_SSI_SIER_ROE1_EN) + /** * fsl_ssi_private: per-SSI private data * @@ -140,7 +147,7 @@ static irqreturn_t fsl_ssi_isr(int irq, void *dev_id) were interrupted for. We mask it with the Interrupt Enable register so that we only check for events that we're interested in. */ - sisr = in_be32(&ssi->sisr) & in_be32(&ssi->sier); + sisr = in_be32(&ssi->sisr) & SIER_FLAGS; if (sisr & CCSR_SSI_SISR_RFRC) { ssi_private->stats.rfrc++; @@ -324,12 +331,7 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, */ /* 4. Enable the interrupts and DMA requests */ - out_be32(&ssi->sier, - CCSR_SSI_SIER_TFRC_EN | CCSR_SSI_SIER_TDMAE | - CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TUE0_EN | - CCSR_SSI_SIER_TUE1_EN | CCSR_SSI_SIER_RFRC_EN | - CCSR_SSI_SIER_RDMAE | CCSR_SSI_SIER_RIE | - CCSR_SSI_SIER_ROE0_EN | CCSR_SSI_SIER_ROE1_EN); + out_be32(&ssi->sier, SIER_FLAGS); /* * Set the watermark for transmit FIFI 0 and receive FIFO 0. We @@ -466,28 +468,12 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_START: clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) setbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE); - } else { - long timeout = jiffies + 10; - + else setbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_RE); - - /* Wait until the SSI has filled its FIFO. Without this - * delay, ALSA complains about overruns. When the FIFO - * is full, the DMA controller initiates its first - * transfer. Until then, however, the DMA's DAR - * register is zero, which translates to an - * out-of-bounds pointer. This makes ALSA think an - * overrun has occurred. - */ - while (!(in_be32(&ssi->sisr) & CCSR_SSI_SISR_RFF0) && - (jiffies < timeout)); - if (!(in_be32(&ssi->sisr) & CCSR_SSI_SISR_RFF0)) - return -EIO; - } break; case SNDRV_PCM_TRIGGER_STOP: @@ -606,39 +592,52 @@ static struct snd_soc_dai fsl_ssi_dai_template = { .ops = &fsl_ssi_dai_ops, }; +/* Show the statistics of a flag only if its interrupt is enabled. The + * compiler will optimze this code to a no-op if the interrupt is not + * enabled. + */ +#define SIER_SHOW(flag, name) \ + do { \ + if (SIER_FLAGS & CCSR_SSI_SIER_##flag) \ + length += sprintf(buf + length, #name "=%u\n", \ + ssi_private->stats.name); \ + } while (0) + + /** * fsl_sysfs_ssi_show: display SSI statistics * - * Display the statistics for the current SSI device. + * Display the statistics for the current SSI device. To avoid confusion, + * we only show those counts that are enabled. */ static ssize_t fsl_sysfs_ssi_show(struct device *dev, struct device_attribute *attr, char *buf) { struct fsl_ssi_private *ssi_private = - container_of(attr, struct fsl_ssi_private, dev_attr); - ssize_t length; - - length = sprintf(buf, "rfrc=%u", ssi_private->stats.rfrc); - length += sprintf(buf + length, "\ttfrc=%u", ssi_private->stats.tfrc); - length += sprintf(buf + length, "\tcmdau=%u", ssi_private->stats.cmdau); - length += sprintf(buf + length, "\tcmddu=%u", ssi_private->stats.cmddu); - length += sprintf(buf + length, "\trxt=%u", ssi_private->stats.rxt); - length += sprintf(buf + length, "\trdr1=%u", ssi_private->stats.rdr1); - length += sprintf(buf + length, "\trdr0=%u", ssi_private->stats.rdr0); - length += sprintf(buf + length, "\ttde1=%u", ssi_private->stats.tde1); - length += sprintf(buf + length, "\ttde0=%u", ssi_private->stats.tde0); - length += sprintf(buf + length, "\troe1=%u", ssi_private->stats.roe1); - length += sprintf(buf + length, "\troe0=%u", ssi_private->stats.roe0); - length += sprintf(buf + length, "\ttue1=%u", ssi_private->stats.tue1); - length += sprintf(buf + length, "\ttue0=%u", ssi_private->stats.tue0); - length += sprintf(buf + length, "\ttfs=%u", ssi_private->stats.tfs); - length += sprintf(buf + length, "\trfs=%u", ssi_private->stats.rfs); - length += sprintf(buf + length, "\ttls=%u", ssi_private->stats.tls); - length += sprintf(buf + length, "\trls=%u", ssi_private->stats.rls); - length += sprintf(buf + length, "\trff1=%u", ssi_private->stats.rff1); - length += sprintf(buf + length, "\trff0=%u", ssi_private->stats.rff0); - length += sprintf(buf + length, "\ttfe1=%u", ssi_private->stats.tfe1); - length += sprintf(buf + length, "\ttfe0=%u\n", ssi_private->stats.tfe0); + container_of(attr, struct fsl_ssi_private, dev_attr); + ssize_t length = 0; + + SIER_SHOW(RFRC_EN, rfrc); + SIER_SHOW(TFRC_EN, tfrc); + SIER_SHOW(CMDAU_EN, cmdau); + SIER_SHOW(CMDDU_EN, cmddu); + SIER_SHOW(RXT_EN, rxt); + SIER_SHOW(RDR1_EN, rdr1); + SIER_SHOW(RDR0_EN, rdr0); + SIER_SHOW(TDE1_EN, tde1); + SIER_SHOW(TDE0_EN, tde0); + SIER_SHOW(ROE1_EN, roe1); + SIER_SHOW(ROE0_EN, roe0); + SIER_SHOW(TUE1_EN, tue1); + SIER_SHOW(TUE0_EN, tue0); + SIER_SHOW(TFS_EN, tfs); + SIER_SHOW(RFS_EN, rfs); + SIER_SHOW(TLS_EN, tls); + SIER_SHOW(RLS_EN, rls); + SIER_SHOW(RFF1_EN, rff1); + SIER_SHOW(RFF0_EN, rff0); + SIER_SHOW(TFE1_EN, tfe1); + SIER_SHOW(TFE0_EN, tfe0); return length; } diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index d6882be3345..9c09b94f0cf 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -146,6 +146,17 @@ static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream, struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); int err = 0; + if (cpu_is_omap343x() && mcbsp_data->bus_id == 1) { + /* + * McBSP2 in OMAP3 has 1024 * 32-bit internal audio buffer. + * Set constraint for minimum buffer size to the same than FIFO + * size in order to avoid underruns in playback startup because + * HW is keeping the DMA request active until FIFO is filled. + */ + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4096, UINT_MAX); + } + if (!cpu_dai->active) err = omap_mcbsp_request(mcbsp_data->bus_id); diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index 5998ab366e8..ad8a10fe629 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -116,6 +116,16 @@ config SND_SOC_ZYLONITE Say Y if you want to add support for SoC audio on the Marvell Zylonite reference platform. +config SND_PXA2XX_SOC_MAGICIAN + tristate "SoC Audio support for HTC Magician" + depends on SND_PXA2XX_SOC && MACH_MAGICIAN + select SND_PXA2XX_SOC_I2S + select SND_PXA_SOC_SSP + select SND_SOC_UDA1380 + help + Say Y if you want to add support for SoC audio on the + HTC Magician. + config SND_PXA2XX_SOC_MIOA701 tristate "SoC Audio support for MIO A701" depends on SND_PXA2XX_SOC && MACH_MIOA701 diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index 8ed881c5e5c..4b90c3ccae4 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -20,6 +20,7 @@ snd-soc-spitz-objs := spitz.o snd-soc-em-x270-objs := em-x270.o snd-soc-palm27x-objs := palm27x.o snd-soc-zylonite-objs := zylonite.o +snd-soc-magician-objs := magician.o snd-soc-mioa701-objs := mioa701_wm9713.o obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o @@ -31,5 +32,6 @@ obj-$(CONFIG_SND_PXA2XX_SOC_E800) += snd-soc-e800.o obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o obj-$(CONFIG_SND_PXA2XX_SOC_EM_X270) += snd-soc-em-x270.o obj-$(CONFIG_SND_PXA2XX_SOC_PALM27X) += snd-soc-palm27x.o +obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c new file mode 100644 index 00000000000..f7c4544f785 --- /dev/null +++ b/sound/soc/pxa/magician.c @@ -0,0 +1,560 @@ +/* + * SoC audio for HTC Magician + * + * Copyright (c) 2006 Philipp Zabel <philipp.zabel@gmail.com> + * + * based on spitz.c, + * Authors: Liam Girdwood <lrg@slimlogic.co.uk> + * Richard Purdie <richard@openedhand.com> + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/gpio.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <mach/pxa-regs.h> +#include <mach/hardware.h> +#include <mach/magician.h> +#include <asm/mach-types.h> +#include "../codecs/uda1380.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-i2s.h" +#include "pxa-ssp.h" + +#define MAGICIAN_MIC 0 +#define MAGICIAN_MIC_EXT 1 + +static int magician_hp_switch; +static int magician_spk_switch = 1; +static int magician_in_sel = MAGICIAN_MIC; + +static void magician_ext_control(struct snd_soc_codec *codec) +{ + if (magician_spk_switch) + snd_soc_dapm_enable_pin(codec, "Speaker"); + else + snd_soc_dapm_disable_pin(codec, "Speaker"); + if (magician_hp_switch) + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + + switch (magician_in_sel) { + case MAGICIAN_MIC: + snd_soc_dapm_disable_pin(codec, "Headset Mic"); + snd_soc_dapm_enable_pin(codec, "Call Mic"); + break; + case MAGICIAN_MIC_EXT: + snd_soc_dapm_disable_pin(codec, "Call Mic"); + snd_soc_dapm_enable_pin(codec, "Headset Mic"); + break; + } + + snd_soc_dapm_sync(codec); +} + +static int magician_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->socdev->card->codec; + + /* check the jack status at stream startup */ + magician_ext_control(codec); + + return 0; +} + +/* + * Magician uses SSP port for playback. + */ +static int magician_playback_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_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + unsigned int acps, acds, width, rate; + unsigned int div4 = PXA_SSP_CLK_SCDB_4; + int ret = 0; + + rate = params_rate(params); + width = snd_pcm_format_physical_width(params_format(params)); + + /* + * rate = SSPSCLK / (2 * width(16 or 32)) + * SSPSCLK = (ACPS / ACDS) / SSPSCLKDIV(div4 or div1) + */ + switch (params_rate(params)) { + case 8000: + /* off by a factor of 2: bug in the PXA27x audio clock? */ + acps = 32842000; + switch (width) { + case 16: + /* 513156 Hz ~= _2_ * 8000 Hz * 32 (+0.23%) */ + acds = PXA_SSP_CLK_AUDIO_DIV_16; + break; + case 32: + /* 1026312 Hz ~= _2_ * 8000 Hz * 64 (+0.23%) */ + acds = PXA_SSP_CLK_AUDIO_DIV_8; + } + break; + case 11025: + acps = 5622000; + switch (width) { + case 16: + /* 351375 Hz ~= 11025 Hz * 32 (-0.41%) */ + acds = PXA_SSP_CLK_AUDIO_DIV_4; + break; + case 32: + /* 702750 Hz ~= 11025 Hz * 64 (-0.41%) */ + acds = PXA_SSP_CLK_AUDIO_DIV_2; + } + break; + case 22050: + acps = 5622000; + switch (width) { + case 16: + /* 702750 Hz ~= 22050 Hz * 32 (-0.41%) */ + acds = PXA_SSP_CLK_AUDIO_DIV_2; + break; + case 32: + /* 1405500 Hz ~= 22050 Hz * 64 (-0.41%) */ + acds = PXA_SSP_CLK_AUDIO_DIV_1; + } + break; + case 44100: + acps = 5622000; + switch (width) { + case 16: + /* 1405500 Hz ~= 44100 Hz * 32 (-0.41%) */ + acds = PXA_SSP_CLK_AUDIO_DIV_2; + break; + case 32: + /* 2811000 Hz ~= 44100 Hz * 64 (-0.41%) */ + acds = PXA_SSP_CLK_AUDIO_DIV_1; + } + break; + case 48000: + acps = 12235000; + switch (width) { + case 16: + /* 1529375 Hz ~= 48000 Hz * 32 (-0.44%) */ + acds = PXA_SSP_CLK_AUDIO_DIV_2; + break; + case 32: + /* 3058750 Hz ~= 48000 Hz * 64 (-0.44%) */ + acds = PXA_SSP_CLK_AUDIO_DIV_1; + } + break; + case 96000: + acps = 12235000; + switch (width) { + case 16: + /* 3058750 Hz ~= 96000 Hz * 32 (-0.44%) */ + acds = PXA_SSP_CLK_AUDIO_DIV_1; + break; + case 32: + /* 6117500 Hz ~= 96000 Hz * 64 (-0.44%) */ + acds = PXA_SSP_CLK_AUDIO_DIV_2; + div4 = PXA_SSP_CLK_SCDB_1; + break; + } + break; + } + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_MSB | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 1, 1); + if (ret < 0) + return ret; + + /* set audio clock as clock source */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_AUDIO, 0, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + /* set the SSP audio system clock ACDS divider */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, + PXA_SSP_AUDIO_DIV_ACDS, acds); + if (ret < 0) + return ret; + + /* set the SSP audio system clock SCDB divider4 */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, + PXA_SSP_AUDIO_DIV_SCDB, div4); + if (ret < 0) + return ret; + + /* set SSP audio pll clock */ + ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, acps); + if (ret < 0) + return ret; + + return 0; +} + +/* + * Magician uses I2S for capture. + */ +static int magician_capture_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_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret = 0; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the I2S system clock as output */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops magician_capture_ops = { + .startup = magician_startup, + .hw_params = magician_capture_hw_params, +}; + +static struct snd_soc_ops magician_playback_ops = { + .startup = magician_startup, + .hw_params = magician_playback_hw_params, +}; + +static int magician_get_hp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = magician_hp_switch; + return 0; +} + +static int magician_set_hp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (magician_hp_switch == ucontrol->value.integer.value[0]) + return 0; + + magician_hp_switch = ucontrol->value.integer.value[0]; + magician_ext_control(codec); + return 1; +} + +static int magician_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = magician_spk_switch; + return 0; +} + +static int magician_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (magician_spk_switch == ucontrol->value.integer.value[0]) + return 0; + + magician_spk_switch = ucontrol->value.integer.value[0]; + magician_ext_control(codec); + return 1; +} + +static int magician_get_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = magician_in_sel; + return 0; +} + +static int magician_set_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (magician_in_sel == ucontrol->value.integer.value[0]) + return 0; + + magician_in_sel = ucontrol->value.integer.value[0]; + + switch (magician_in_sel) { + case MAGICIAN_MIC: + gpio_set_value(EGPIO_MAGICIAN_IN_SEL1, 1); + break; + case MAGICIAN_MIC_EXT: + gpio_set_value(EGPIO_MAGICIAN_IN_SEL1, 0); + } + + return 1; +} + +static int magician_spk_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + gpio_set_value(EGPIO_MAGICIAN_SPK_POWER, SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +static int magician_hp_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + gpio_set_value(EGPIO_MAGICIAN_EP_POWER, SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +static int magician_mic_bias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + gpio_set_value(EGPIO_MAGICIAN_MIC_POWER, SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +/* magician machine dapm widgets */ +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", magician_hp_power), + SND_SOC_DAPM_SPK("Speaker", magician_spk_power), + SND_SOC_DAPM_MIC("Call Mic", magician_mic_bias), + SND_SOC_DAPM_MIC("Headset Mic", magician_mic_bias), +}; + +/* magician machine audio_map */ +static const struct snd_soc_dapm_route audio_map[] = { + + /* Headphone connected to VOUTL, VOUTR */ + {"Headphone Jack", NULL, "VOUTL"}, + {"Headphone Jack", NULL, "VOUTR"}, + + /* Speaker connected to VOUTL, VOUTR */ + {"Speaker", NULL, "VOUTL"}, + {"Speaker", NULL, "VOUTR"}, + + /* Mics are connected to VINM */ + {"VINM", NULL, "Headset Mic"}, + {"VINM", NULL, "Call Mic"}, +}; + +static const char *input_select[] = {"Call Mic", "Headset Mic"}; +static const struct soc_enum magician_in_sel_enum = + SOC_ENUM_SINGLE_EXT(2, input_select); + +static const struct snd_kcontrol_new uda1380_magician_controls[] = { + SOC_SINGLE_BOOL_EXT("Headphone Switch", + (unsigned long)&magician_hp_switch, + magician_get_hp, magician_set_hp), + SOC_SINGLE_BOOL_EXT("Speaker Switch", + (unsigned long)&magician_spk_switch, + magician_get_spk, magician_set_spk), + SOC_ENUM_EXT("Input Select", magician_in_sel_enum, + magician_get_input, magician_set_input), +}; + +/* + * Logic for a uda1380 as connected on a HTC Magician + */ +static int magician_uda1380_init(struct snd_soc_codec *codec) +{ + int err; + + /* NC codec pins */ + snd_soc_dapm_nc_pin(codec, "VOUTLHP"); + snd_soc_dapm_nc_pin(codec, "VOUTRHP"); + + /* FIXME: is anything connected here? */ + snd_soc_dapm_nc_pin(codec, "VINL"); + snd_soc_dapm_nc_pin(codec, "VINR"); + + /* Add magician specific controls */ + err = snd_soc_add_controls(codec, uda1380_magician_controls, + ARRAY_SIZE(uda1380_magician_controls)); + if (err < 0) + return err; + + /* Add magician specific widgets */ + snd_soc_dapm_new_controls(codec, uda1380_dapm_widgets, + ARRAY_SIZE(uda1380_dapm_widgets)); + + /* Set up magician specific audio path interconnects */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(codec); + return 0; +} + +/* magician digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link magician_dai[] = { +{ + .name = "uda1380", + .stream_name = "UDA1380 Playback", + .cpu_dai = &pxa_ssp_dai[PXA_DAI_SSP1], + .codec_dai = &uda1380_dai[UDA1380_DAI_PLAYBACK], + .init = magician_uda1380_init, + .ops = &magician_playback_ops, +}, +{ + .name = "uda1380", + .stream_name = "UDA1380 Capture", + .cpu_dai = &pxa_i2s_dai, + .codec_dai = &uda1380_dai[UDA1380_DAI_CAPTURE], + .ops = &magician_capture_ops, +} +}; + +/* magician audio machine driver */ +static struct snd_soc_card snd_soc_card_magician = { + .name = "Magician", + .dai_link = magician_dai, + .num_links = ARRAY_SIZE(magician_dai), + .platform = &pxa2xx_soc_platform, +}; + +/* magician audio private data */ +static struct uda1380_setup_data magician_uda1380_setup = { + .i2c_address = 0x18, + .dac_clk = UDA1380_DAC_CLK_WSPLL, +}; + +/* magician audio subsystem */ +static struct snd_soc_device magician_snd_devdata = { + .card = &snd_soc_card_magician, + .codec_dev = &soc_codec_dev_uda1380, + .codec_data = &magician_uda1380_setup, +}; + +static struct platform_device *magician_snd_device; + +static int __init magician_init(void) +{ + int ret; + + if (!machine_is_magician()) + return -ENODEV; + + ret = gpio_request(EGPIO_MAGICIAN_CODEC_POWER, "CODEC_POWER"); + if (ret) + goto err_request_power; + ret = gpio_request(EGPIO_MAGICIAN_CODEC_RESET, "CODEC_RESET"); + if (ret) + goto err_request_reset; + ret = gpio_request(EGPIO_MAGICIAN_SPK_POWER, "SPK_POWER"); + if (ret) + goto err_request_spk; + ret = gpio_request(EGPIO_MAGICIAN_EP_POWER, "EP_POWER"); + if (ret) + goto err_request_ep; + ret = gpio_request(EGPIO_MAGICIAN_MIC_POWER, "MIC_POWER"); + if (ret) + goto err_request_mic; + ret = gpio_request(EGPIO_MAGICIAN_IN_SEL0, "IN_SEL0"); + if (ret) + goto err_request_in_sel0; + ret = gpio_request(EGPIO_MAGICIAN_IN_SEL1, "IN_SEL1"); + if (ret) + goto err_request_in_sel1; + + gpio_set_value(EGPIO_MAGICIAN_CODEC_POWER, 1); + gpio_set_value(EGPIO_MAGICIAN_IN_SEL0, 0); + + /* we may need to have the clock running here - pH5 */ + gpio_set_value(EGPIO_MAGICIAN_CODEC_RESET, 1); + udelay(5); + gpio_set_value(EGPIO_MAGICIAN_CODEC_RESET, 0); + + magician_snd_device = platform_device_alloc("soc-audio", -1); + if (!magician_snd_device) { + ret = -ENOMEM; + goto err_pdev; + } + + platform_set_drvdata(magician_snd_device, &magician_snd_devdata); + magician_snd_devdata.dev = &magician_snd_device->dev; + ret = platform_device_add(magician_snd_device); + if (ret) { + platform_device_put(magician_snd_device); + goto err_pdev; + } + + return 0; + +err_pdev: + gpio_free(EGPIO_MAGICIAN_IN_SEL1); +err_request_in_sel1: + gpio_free(EGPIO_MAGICIAN_IN_SEL0); +err_request_in_sel0: + gpio_free(EGPIO_MAGICIAN_MIC_POWER); +err_request_mic: + gpio_free(EGPIO_MAGICIAN_EP_POWER); +err_request_ep: + gpio_free(EGPIO_MAGICIAN_SPK_POWER); +err_request_spk: + gpio_free(EGPIO_MAGICIAN_CODEC_RESET); +err_request_reset: + gpio_free(EGPIO_MAGICIAN_CODEC_POWER); +err_request_power: + return ret; +} + +static void __exit magician_exit(void) +{ + platform_device_unregister(magician_snd_device); + + gpio_set_value(EGPIO_MAGICIAN_SPK_POWER, 0); + gpio_set_value(EGPIO_MAGICIAN_EP_POWER, 0); + gpio_set_value(EGPIO_MAGICIAN_MIC_POWER, 0); + gpio_set_value(EGPIO_MAGICIAN_CODEC_POWER, 0); + + gpio_free(EGPIO_MAGICIAN_IN_SEL1); + gpio_free(EGPIO_MAGICIAN_IN_SEL0); + gpio_free(EGPIO_MAGICIAN_MIC_POWER); + gpio_free(EGPIO_MAGICIAN_EP_POWER); + gpio_free(EGPIO_MAGICIAN_SPK_POWER); + gpio_free(EGPIO_MAGICIAN_CODEC_RESET); + gpio_free(EGPIO_MAGICIAN_CODEC_POWER); +} + +module_init(magician_init); +module_exit(magician_exit); + +MODULE_AUTHOR("Philipp Zabel"); +MODULE_DESCRIPTION("ALSA SoC Magician"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index 7acd3febf8b..308a657928d 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -627,12 +627,18 @@ static int pxa_ssp_hw_params(struct snd_pcm_substream *substream, u32 sscr0; u32 sspsp; int width = snd_pcm_format_physical_width(params_format(params)); + int ttsa = ssp_read_reg(ssp, SSTSA) & 0xf; /* select correct DMA params */ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) dma = 1; /* capture DMA offset is 1,3 */ - if (chn == 2) - dma += 2; /* stereo DMA offset is 2, mono is 0 */ + /* Network mode with one active slot (ttsa == 1) can be used + * to force 16-bit frame width on the wire (for S16_LE), even + * with two channels. Use 16-bit DMA transfers for this case. + */ + if (((chn == 2) && (ttsa != 1)) || (width == 32)) + dma += 2; /* 32-bit DMA offset is 2, 16-bit is 0 */ + cpu_dai->dma_data = ssp_dma_params[cpu_dai->id][dma]; dev_dbg(&ssp->pdev->dev, "pxa_ssp_hw_params: dma %d\n", dma); @@ -712,7 +718,7 @@ static int pxa_ssp_hw_params(struct snd_pcm_substream *substream, /* When we use a network mode, we always require TDM slots * - complain loudly and fail if they've not been set up yet. */ - if ((sscr0 & SSCR0_MOD) && !(ssp_read_reg(ssp, SSTSA) & 0xf)) { + if ((sscr0 & SSCR0_MOD) && !ttsa) { dev_err(&ssp->pdev->dev, "No TDM timeslot configured\n"); return -EINVAL; } diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 6e710f705a7..99712f652d0 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -98,7 +98,7 @@ static int soc_ac97_dev_register(struct snd_soc_codec *codec) int err; codec->ac97->dev.bus = &ac97_bus_type; - codec->ac97->dev.parent = NULL; + codec->ac97->dev.parent = codec->card->dev; codec->ac97->dev.release = soc_ac97_device_release; dev_set_name(&codec->ac97->dev, "%d-%d:%s", @@ -767,11 +767,21 @@ static int soc_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_card *card = socdev->card; + struct snd_soc_dai *cpu_dai = card->dai_link[0].cpu_dai; - dev_dbg(socdev->dev, "scheduling resume work\n"); - - if (!schedule_work(&card->deferred_resume_work)) - dev_err(socdev->dev, "resume work item may be lost\n"); + /* AC97 devices might have other drivers hanging off them so + * need to resume immediately. Other drivers don't have that + * problem and may take a substantial amount of time to resume + * due to I/O costs and anti-pop so handle them out of line. + */ + if (cpu_dai->ac97_control) { + dev_dbg(socdev->dev, "Resuming AC97 immediately\n"); + soc_resume_deferred(&card->deferred_resume_work); + } else { + dev_dbg(socdev->dev, "Scheduling resume work\n"); + if (!schedule_work(&card->deferred_resume_work)) + dev_err(socdev->dev, "resume work item may be lost\n"); + } return 0; } |