mirror of
https://github.com/torvalds/linux.git
synced 2024-12-25 04:11:49 +00:00
f0fba2ad1b
This patch extends the ASoC API to allow sound cards to have more than one CODEC and more than one platform DMA controller. This is achieved by dividing some current ASoC structures that contain both driver data and device data into structures that only either contain device data or driver data. i.e. struct snd_soc_codec ---> struct snd_soc_codec (device data) +-> struct snd_soc_codec_driver (driver data) struct snd_soc_platform ---> struct snd_soc_platform (device data) +-> struct snd_soc_platform_driver (driver data) struct snd_soc_dai ---> struct snd_soc_dai (device data) +-> struct snd_soc_dai_driver (driver data) struct snd_soc_device ---> deleted This now allows ASoC to be more tightly aligned with the Linux driver model and also means that every ASoC codec, platform and (platform) DAI is a kernel device. ASoC component private data is now stored as device private data. The ASoC sound card struct snd_soc_card has also been updated to store lists of it's components rather than a pointer to a codec and platform. The PCM runtime struct soc_pcm_runtime now has pointers to all its components. This patch adds DAPM support for ASoC multi-component and removes struct snd_soc_socdev from DAPM core. All DAPM calls are now made on a card, codec or runtime PCM level basis rather than using snd_soc_socdev. Other notable multi-component changes:- * Stream operations now de-reference less structures. * close_delayed work() now runs on a DAI basis rather than looping all DAIs in a card. * PM suspend()/resume() operations can now handle N CODECs and Platforms per sound card. * Added soc_bind_dai_link() to bind the component devices to the sound card. * Added soc_dai_link_probe() and soc_dai_link_remove() to probe and remove DAI link components. * sysfs entries can now be registered per component per card. * snd_soc_new_pcms() functionailty rolled into dai_link_probe(). * snd_soc_register_codec() now does all the codec list and mutex init. This patch changes the probe() and remove() of the CODEC drivers as follows:- o Make CODEC driver a platform driver o Moved all struct snd_soc_codec list, mutex, etc initialiasation to core. o Removed all static codec pointers (drivers now support > 1 codec dev) o snd_soc_register_pcms() now done by core. o snd_soc_register_dai() folded into snd_soc_register_codec(). CS4270 portions: Acked-by: Timur Tabi <timur@freescale.com> Some TLV320aic23 and Cirrus platform fixes. Signed-off-by: Ryan Mallon <ryan@bluewatersys.com> TI CODEC and OMAP fixes Signed-off-by: Peter Ujfalusi <peter.ujfalusi@nokia.com> Signed-off-by: Janusz Krzysztofik <jkrzyszt@tis.icnet.pl> Signed-off-by: Jarkko Nikula <jhnikula@gmail.com> Samsung platform and misc fixes :- Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com> Signed-off-by: Joonyoung Shim <jy0922.shim@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Reviewed-by: Jassi Brar <jassi.brar@samsung.com> Signed-off-by: Seungwhan Youn <sw.youn@samsung.com> MPC8610 and PPC fixes. Signed-off-by: Timur Tabi <timur@freescale.com> i.MX fixes and some core fixes. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de> J4740 platform fixes:- Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> CC: Tony Lindgren <tony@atomide.com> CC: Nicolas Ferre <nicolas.ferre@atmel.com> CC: Kevin Hilman <khilman@deeprootsystems.com> CC: Sascha Hauer <s.hauer@pengutronix.de> CC: Atsushi Nemoto <anemo@mba.ocn.ne.jp> CC: Kuninori Morimoto <morimoto.kuninori@renesas.com> CC: Daniel Gloeckner <dg@emlix.com> CC: Manuel Lauss <mano@roarinelk.homelinux.net> CC: Mike Frysinger <vapier.adi@gmail.com> CC: Arnaud Patard <apatard@mandriva.com> CC: Wan ZongShun <mcuos.com@gmail.com> Acked-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Signed-off-by: Liam Girdwood <lrg@slimlogic.co.uk>
928 lines
26 KiB
C
928 lines
26 KiB
C
/*
|
|
* omap-mcbsp.c -- OMAP ALSA SoC DAI driver using McBSP port
|
|
*
|
|
* Copyright (C) 2008 Nokia Corporation
|
|
*
|
|
* Contact: Jarkko Nikula <jhnikula@gmail.com>
|
|
* Peter Ujfalusi <peter.ujfalusi@nokia.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/device.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/initval.h>
|
|
#include <sound/soc.h>
|
|
|
|
#include <plat/control.h>
|
|
#include <plat/dma.h>
|
|
#include <plat/mcbsp.h>
|
|
#include "omap-mcbsp.h"
|
|
#include "omap-pcm.h"
|
|
|
|
#define OMAP_MCBSP_RATES (SNDRV_PCM_RATE_8000_96000)
|
|
|
|
#define OMAP_MCBSP_SOC_SINGLE_S16_EXT(xname, xmin, xmax, \
|
|
xhandler_get, xhandler_put) \
|
|
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
|
.info = omap_mcbsp_st_info_volsw, \
|
|
.get = xhandler_get, .put = xhandler_put, \
|
|
.private_value = (unsigned long) &(struct soc_mixer_control) \
|
|
{.min = xmin, .max = xmax} }
|
|
|
|
struct omap_mcbsp_data {
|
|
unsigned int bus_id;
|
|
struct omap_mcbsp_reg_cfg regs;
|
|
unsigned int fmt;
|
|
/*
|
|
* Flags indicating is the bus already activated and configured by
|
|
* another substream
|
|
*/
|
|
int active;
|
|
int configured;
|
|
unsigned int in_freq;
|
|
int clk_div;
|
|
int wlen;
|
|
};
|
|
|
|
static struct omap_mcbsp_data mcbsp_data[NUM_LINKS];
|
|
|
|
/*
|
|
* Stream DMA parameters. DMA request line and port address are set runtime
|
|
* since they are different between OMAP1 and later OMAPs
|
|
*/
|
|
static struct omap_pcm_dma_data omap_mcbsp_dai_dma_params[NUM_LINKS][2];
|
|
|
|
#if defined(CONFIG_ARCH_OMAP15XX) || defined(CONFIG_ARCH_OMAP16XX)
|
|
static const int omap1_dma_reqs[][2] = {
|
|
{ OMAP_DMA_MCBSP1_TX, OMAP_DMA_MCBSP1_RX },
|
|
{ OMAP_DMA_MCBSP2_TX, OMAP_DMA_MCBSP2_RX },
|
|
{ OMAP_DMA_MCBSP3_TX, OMAP_DMA_MCBSP3_RX },
|
|
};
|
|
static const unsigned long omap1_mcbsp_port[][2] = {
|
|
{ OMAP1510_MCBSP1_BASE + OMAP_MCBSP_REG_DXR1,
|
|
OMAP1510_MCBSP1_BASE + OMAP_MCBSP_REG_DRR1 },
|
|
{ OMAP1510_MCBSP2_BASE + OMAP_MCBSP_REG_DXR1,
|
|
OMAP1510_MCBSP2_BASE + OMAP_MCBSP_REG_DRR1 },
|
|
{ OMAP1510_MCBSP3_BASE + OMAP_MCBSP_REG_DXR1,
|
|
OMAP1510_MCBSP3_BASE + OMAP_MCBSP_REG_DRR1 },
|
|
};
|
|
#else
|
|
static const int omap1_dma_reqs[][2] = {};
|
|
static const unsigned long omap1_mcbsp_port[][2] = {};
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_OMAP2) || defined(CONFIG_ARCH_OMAP3)
|
|
static const int omap24xx_dma_reqs[][2] = {
|
|
{ OMAP24XX_DMA_MCBSP1_TX, OMAP24XX_DMA_MCBSP1_RX },
|
|
{ OMAP24XX_DMA_MCBSP2_TX, OMAP24XX_DMA_MCBSP2_RX },
|
|
#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3)
|
|
{ OMAP24XX_DMA_MCBSP3_TX, OMAP24XX_DMA_MCBSP3_RX },
|
|
{ OMAP24XX_DMA_MCBSP4_TX, OMAP24XX_DMA_MCBSP4_RX },
|
|
{ OMAP24XX_DMA_MCBSP5_TX, OMAP24XX_DMA_MCBSP5_RX },
|
|
#endif
|
|
};
|
|
#else
|
|
static const int omap24xx_dma_reqs[][2] = {};
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_OMAP2420)
|
|
static const unsigned long omap2420_mcbsp_port[][2] = {
|
|
{ OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR1,
|
|
OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR1 },
|
|
{ OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR1,
|
|
OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR1 },
|
|
};
|
|
#else
|
|
static const unsigned long omap2420_mcbsp_port[][2] = {};
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_OMAP2430)
|
|
static const unsigned long omap2430_mcbsp_port[][2] = {
|
|
{ OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR,
|
|
OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR },
|
|
{ OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR,
|
|
OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR },
|
|
{ OMAP2430_MCBSP3_BASE + OMAP_MCBSP_REG_DXR,
|
|
OMAP2430_MCBSP3_BASE + OMAP_MCBSP_REG_DRR },
|
|
{ OMAP2430_MCBSP4_BASE + OMAP_MCBSP_REG_DXR,
|
|
OMAP2430_MCBSP4_BASE + OMAP_MCBSP_REG_DRR },
|
|
{ OMAP2430_MCBSP5_BASE + OMAP_MCBSP_REG_DXR,
|
|
OMAP2430_MCBSP5_BASE + OMAP_MCBSP_REG_DRR },
|
|
};
|
|
#else
|
|
static const unsigned long omap2430_mcbsp_port[][2] = {};
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_OMAP3)
|
|
static const unsigned long omap34xx_mcbsp_port[][2] = {
|
|
{ OMAP34XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR,
|
|
OMAP34XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR },
|
|
{ OMAP34XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR,
|
|
OMAP34XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR },
|
|
{ OMAP34XX_MCBSP3_BASE + OMAP_MCBSP_REG_DXR,
|
|
OMAP34XX_MCBSP3_BASE + OMAP_MCBSP_REG_DRR },
|
|
{ OMAP34XX_MCBSP4_BASE + OMAP_MCBSP_REG_DXR,
|
|
OMAP34XX_MCBSP4_BASE + OMAP_MCBSP_REG_DRR },
|
|
{ OMAP34XX_MCBSP5_BASE + OMAP_MCBSP_REG_DXR,
|
|
OMAP34XX_MCBSP5_BASE + OMAP_MCBSP_REG_DRR },
|
|
};
|
|
#else
|
|
static const unsigned long omap34xx_mcbsp_port[][2] = {};
|
|
#endif
|
|
|
|
static void omap_mcbsp_set_threshold(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
|
struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
struct omap_pcm_dma_data *dma_data;
|
|
int dma_op_mode = omap_mcbsp_get_dma_op_mode(mcbsp_data->bus_id);
|
|
int words;
|
|
|
|
dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
|
|
|
|
/* TODO: Currently, MODE_ELEMENT == MODE_FRAME */
|
|
if (dma_op_mode == MCBSP_DMA_MODE_THRESHOLD)
|
|
/*
|
|
* Configure McBSP threshold based on either:
|
|
* packet_size, when the sDMA is in packet mode, or
|
|
* based on the period size.
|
|
*/
|
|
if (dma_data->packet_size)
|
|
words = dma_data->packet_size;
|
|
else
|
|
words = snd_pcm_lib_period_bytes(substream) /
|
|
(mcbsp_data->wlen / 8);
|
|
else
|
|
words = 1;
|
|
|
|
/* Configure McBSP internal buffer usage */
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
omap_mcbsp_set_tx_threshold(mcbsp_data->bus_id, words);
|
|
else
|
|
omap_mcbsp_set_rx_threshold(mcbsp_data->bus_id, words);
|
|
}
|
|
|
|
static int omap_mcbsp_hwrule_min_buffersize(struct snd_pcm_hw_params *params,
|
|
struct snd_pcm_hw_rule *rule)
|
|
{
|
|
struct snd_interval *buffer_size = hw_param_interval(params,
|
|
SNDRV_PCM_HW_PARAM_BUFFER_SIZE);
|
|
struct snd_interval *channels = hw_param_interval(params,
|
|
SNDRV_PCM_HW_PARAM_CHANNELS);
|
|
struct omap_mcbsp_data *mcbsp_data = rule->private;
|
|
struct snd_interval frames;
|
|
int size;
|
|
|
|
snd_interval_any(&frames);
|
|
size = omap_mcbsp_get_fifo_size(mcbsp_data->bus_id);
|
|
|
|
frames.min = size / channels->min;
|
|
frames.integer = 1;
|
|
return snd_interval_refine(buffer_size, &frames);
|
|
}
|
|
|
|
static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *cpu_dai)
|
|
{
|
|
struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
int bus_id = mcbsp_data->bus_id;
|
|
int err = 0;
|
|
|
|
if (!cpu_dai->active)
|
|
err = omap_mcbsp_request(bus_id);
|
|
|
|
/*
|
|
* OMAP3 McBSP FIFO is word structured.
|
|
* McBSP2 has 1024 + 256 = 1280 word long buffer,
|
|
* McBSP1,3,4,5 has 128 word long buffer
|
|
* This means that the size of the FIFO depends on the sample format.
|
|
* For example on McBSP3:
|
|
* 16bit samples: size is 128 * 2 = 256 bytes
|
|
* 32bit samples: size is 128 * 4 = 512 bytes
|
|
* It is simpler to place constraint for buffer and period based on
|
|
* channels.
|
|
* McBSP3 as example again (16 or 32 bit samples):
|
|
* 1 channel (mono): size is 128 frames (128 words)
|
|
* 2 channels (stereo): size is 128 / 2 = 64 frames (2 * 64 words)
|
|
* 4 channels: size is 128 / 4 = 32 frames (4 * 32 words)
|
|
*/
|
|
if (cpu_is_omap343x()) {
|
|
/*
|
|
* Rule for the buffer size. We should not allow
|
|
* smaller buffer than the FIFO size to avoid underruns
|
|
*/
|
|
snd_pcm_hw_rule_add(substream->runtime, 0,
|
|
SNDRV_PCM_HW_PARAM_CHANNELS,
|
|
omap_mcbsp_hwrule_min_buffersize,
|
|
mcbsp_data,
|
|
SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1);
|
|
|
|
/* Make sure, that the period size is always even */
|
|
snd_pcm_hw_constraint_step(substream->runtime, 0,
|
|
SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static void omap_mcbsp_dai_shutdown(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *cpu_dai)
|
|
{
|
|
struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
|
|
if (!cpu_dai->active) {
|
|
omap_mcbsp_free(mcbsp_data->bus_id);
|
|
mcbsp_data->configured = 0;
|
|
}
|
|
}
|
|
|
|
static int omap_mcbsp_dai_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
struct snd_soc_dai *cpu_dai)
|
|
{
|
|
struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
int err = 0, play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
mcbsp_data->active++;
|
|
omap_mcbsp_start(mcbsp_data->bus_id, play, !play);
|
|
break;
|
|
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
omap_mcbsp_stop(mcbsp_data->bus_id, play, !play);
|
|
mcbsp_data->active--;
|
|
break;
|
|
default:
|
|
err = -EINVAL;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static snd_pcm_sframes_t omap_mcbsp_dai_delay(
|
|
struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
|
struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
u16 fifo_use;
|
|
snd_pcm_sframes_t delay;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
fifo_use = omap_mcbsp_get_tx_delay(mcbsp_data->bus_id);
|
|
else
|
|
fifo_use = omap_mcbsp_get_rx_delay(mcbsp_data->bus_id);
|
|
|
|
/*
|
|
* Divide the used locations with the channel count to get the
|
|
* FIFO usage in samples (don't care about partial samples in the
|
|
* buffer).
|
|
*/
|
|
delay = fifo_use / substream->runtime->channels;
|
|
|
|
return delay;
|
|
}
|
|
|
|
static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_soc_dai *cpu_dai)
|
|
{
|
|
struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
|
|
struct omap_pcm_dma_data *dma_data;
|
|
int dma, bus_id = mcbsp_data->bus_id;
|
|
int wlen, channels, wpf, sync_mode = OMAP_DMA_SYNC_ELEMENT;
|
|
int pkt_size = 0;
|
|
unsigned long port;
|
|
unsigned int format, div, framesize, master;
|
|
|
|
dma_data = &omap_mcbsp_dai_dma_params[cpu_dai->id][substream->stream];
|
|
if (cpu_class_is_omap1()) {
|
|
dma = omap1_dma_reqs[bus_id][substream->stream];
|
|
port = omap1_mcbsp_port[bus_id][substream->stream];
|
|
} else if (cpu_is_omap2420()) {
|
|
dma = omap24xx_dma_reqs[bus_id][substream->stream];
|
|
port = omap2420_mcbsp_port[bus_id][substream->stream];
|
|
} else if (cpu_is_omap2430()) {
|
|
dma = omap24xx_dma_reqs[bus_id][substream->stream];
|
|
port = omap2430_mcbsp_port[bus_id][substream->stream];
|
|
} else if (cpu_is_omap343x()) {
|
|
dma = omap24xx_dma_reqs[bus_id][substream->stream];
|
|
port = omap34xx_mcbsp_port[bus_id][substream->stream];
|
|
} else {
|
|
return -ENODEV;
|
|
}
|
|
switch (params_format(params)) {
|
|
case SNDRV_PCM_FORMAT_S16_LE:
|
|
dma_data->data_type = OMAP_DMA_DATA_TYPE_S16;
|
|
wlen = 16;
|
|
break;
|
|
case SNDRV_PCM_FORMAT_S32_LE:
|
|
dma_data->data_type = OMAP_DMA_DATA_TYPE_S32;
|
|
wlen = 32;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
if (cpu_is_omap343x()) {
|
|
dma_data->set_threshold = omap_mcbsp_set_threshold;
|
|
/* TODO: Currently, MODE_ELEMENT == MODE_FRAME */
|
|
if (omap_mcbsp_get_dma_op_mode(bus_id) ==
|
|
MCBSP_DMA_MODE_THRESHOLD) {
|
|
int period_words, max_thrsh;
|
|
|
|
period_words = params_period_bytes(params) / (wlen / 8);
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
max_thrsh = omap_mcbsp_get_max_tx_threshold(
|
|
mcbsp_data->bus_id);
|
|
else
|
|
max_thrsh = omap_mcbsp_get_max_rx_threshold(
|
|
mcbsp_data->bus_id);
|
|
/*
|
|
* If the period contains less or equal number of words,
|
|
* we are using the original threshold mode setup:
|
|
* McBSP threshold = sDMA frame size = period_size
|
|
* Otherwise we switch to sDMA packet mode:
|
|
* McBSP threshold = sDMA packet size
|
|
* sDMA frame size = period size
|
|
*/
|
|
if (period_words > max_thrsh) {
|
|
int divider = 0;
|
|
|
|
/*
|
|
* Look for the biggest threshold value, which
|
|
* divides the period size evenly.
|
|
*/
|
|
divider = period_words / max_thrsh;
|
|
if (period_words % max_thrsh)
|
|
divider++;
|
|
while (period_words % divider &&
|
|
divider < period_words)
|
|
divider++;
|
|
if (divider == period_words)
|
|
return -EINVAL;
|
|
|
|
pkt_size = period_words / divider;
|
|
sync_mode = OMAP_DMA_SYNC_PACKET;
|
|
} else {
|
|
sync_mode = OMAP_DMA_SYNC_FRAME;
|
|
}
|
|
}
|
|
}
|
|
|
|
dma_data->name = substream->stream ? "Audio Capture" : "Audio Playback";
|
|
dma_data->dma_req = dma;
|
|
dma_data->port_addr = port;
|
|
dma_data->sync_mode = sync_mode;
|
|
dma_data->packet_size = pkt_size;
|
|
|
|
snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
|
|
|
|
if (mcbsp_data->configured) {
|
|
/* McBSP already configured by another stream */
|
|
return 0;
|
|
}
|
|
|
|
format = mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK;
|
|
wpf = channels = params_channels(params);
|
|
if (channels == 2 && (format == SND_SOC_DAIFMT_I2S ||
|
|
format == SND_SOC_DAIFMT_LEFT_J)) {
|
|
/* Use dual-phase frames */
|
|
regs->rcr2 |= RPHASE;
|
|
regs->xcr2 |= XPHASE;
|
|
/* Set 1 word per (McBSP) frame for phase1 and phase2 */
|
|
wpf--;
|
|
regs->rcr2 |= RFRLEN2(wpf - 1);
|
|
regs->xcr2 |= XFRLEN2(wpf - 1);
|
|
}
|
|
|
|
regs->rcr1 |= RFRLEN1(wpf - 1);
|
|
regs->xcr1 |= XFRLEN1(wpf - 1);
|
|
|
|
switch (params_format(params)) {
|
|
case SNDRV_PCM_FORMAT_S16_LE:
|
|
/* Set word lengths */
|
|
regs->rcr2 |= RWDLEN2(OMAP_MCBSP_WORD_16);
|
|
regs->rcr1 |= RWDLEN1(OMAP_MCBSP_WORD_16);
|
|
regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_16);
|
|
regs->xcr1 |= XWDLEN1(OMAP_MCBSP_WORD_16);
|
|
break;
|
|
case SNDRV_PCM_FORMAT_S32_LE:
|
|
/* Set word lengths */
|
|
regs->rcr2 |= RWDLEN2(OMAP_MCBSP_WORD_32);
|
|
regs->rcr1 |= RWDLEN1(OMAP_MCBSP_WORD_32);
|
|
regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_32);
|
|
regs->xcr1 |= XWDLEN1(OMAP_MCBSP_WORD_32);
|
|
break;
|
|
default:
|
|
/* Unsupported PCM format */
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* In McBSP master modes, FRAME (i.e. sample rate) is generated
|
|
* by _counting_ BCLKs. Calculate frame size in BCLKs */
|
|
master = mcbsp_data->fmt & SND_SOC_DAIFMT_MASTER_MASK;
|
|
if (master == SND_SOC_DAIFMT_CBS_CFS) {
|
|
div = mcbsp_data->clk_div ? mcbsp_data->clk_div : 1;
|
|
framesize = (mcbsp_data->in_freq / div) / params_rate(params);
|
|
|
|
if (framesize < wlen * channels) {
|
|
printk(KERN_ERR "%s: not enough bandwidth for desired rate and "
|
|
"channels\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
} else
|
|
framesize = wlen * channels;
|
|
|
|
/* Set FS period and length in terms of bit clock periods */
|
|
switch (format) {
|
|
case SND_SOC_DAIFMT_I2S:
|
|
case SND_SOC_DAIFMT_LEFT_J:
|
|
regs->srgr2 |= FPER(framesize - 1);
|
|
regs->srgr1 |= FWID((framesize >> 1) - 1);
|
|
break;
|
|
case SND_SOC_DAIFMT_DSP_A:
|
|
case SND_SOC_DAIFMT_DSP_B:
|
|
regs->srgr2 |= FPER(framesize - 1);
|
|
regs->srgr1 |= FWID(0);
|
|
break;
|
|
}
|
|
|
|
omap_mcbsp_config(bus_id, &mcbsp_data->regs);
|
|
mcbsp_data->wlen = wlen;
|
|
mcbsp_data->configured = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This must be called before _set_clkdiv and _set_sysclk since McBSP register
|
|
* cache is initialized here
|
|
*/
|
|
static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
|
unsigned int fmt)
|
|
{
|
|
struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
|
|
unsigned int temp_fmt = fmt;
|
|
|
|
if (mcbsp_data->configured)
|
|
return 0;
|
|
|
|
mcbsp_data->fmt = fmt;
|
|
memset(regs, 0, sizeof(*regs));
|
|
/* Generic McBSP register settings */
|
|
regs->spcr2 |= XINTM(3) | FREE;
|
|
regs->spcr1 |= RINTM(3);
|
|
/* RFIG and XFIG are not defined in 34xx */
|
|
if (!cpu_is_omap34xx()) {
|
|
regs->rcr2 |= RFIG;
|
|
regs->xcr2 |= XFIG;
|
|
}
|
|
if (cpu_is_omap2430() || cpu_is_omap34xx()) {
|
|
regs->xccr = DXENDLY(1) | XDMAEN | XDISABLE;
|
|
regs->rccr = RFULL_CYCLE | RDMAEN | RDISABLE;
|
|
}
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
case SND_SOC_DAIFMT_I2S:
|
|
/* 1-bit data delay */
|
|
regs->rcr2 |= RDATDLY(1);
|
|
regs->xcr2 |= XDATDLY(1);
|
|
break;
|
|
case SND_SOC_DAIFMT_LEFT_J:
|
|
/* 0-bit data delay */
|
|
regs->rcr2 |= RDATDLY(0);
|
|
regs->xcr2 |= XDATDLY(0);
|
|
regs->spcr1 |= RJUST(2);
|
|
/* Invert FS polarity configuration */
|
|
temp_fmt ^= SND_SOC_DAIFMT_NB_IF;
|
|
break;
|
|
case SND_SOC_DAIFMT_DSP_A:
|
|
/* 1-bit data delay */
|
|
regs->rcr2 |= RDATDLY(1);
|
|
regs->xcr2 |= XDATDLY(1);
|
|
/* Invert FS polarity configuration */
|
|
temp_fmt ^= SND_SOC_DAIFMT_NB_IF;
|
|
break;
|
|
case SND_SOC_DAIFMT_DSP_B:
|
|
/* 0-bit data delay */
|
|
regs->rcr2 |= RDATDLY(0);
|
|
regs->xcr2 |= XDATDLY(0);
|
|
/* Invert FS polarity configuration */
|
|
temp_fmt ^= SND_SOC_DAIFMT_NB_IF;
|
|
break;
|
|
default:
|
|
/* Unsupported data format */
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
case SND_SOC_DAIFMT_CBS_CFS:
|
|
/* McBSP master. Set FS and bit clocks as outputs */
|
|
regs->pcr0 |= FSXM | FSRM |
|
|
CLKXM | CLKRM;
|
|
/* Sample rate generator drives the FS */
|
|
regs->srgr2 |= FSGM;
|
|
break;
|
|
case SND_SOC_DAIFMT_CBM_CFM:
|
|
/* McBSP slave */
|
|
break;
|
|
default:
|
|
/* Unsupported master/slave configuration */
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Set bit clock (CLKX/CLKR) and FS polarities */
|
|
switch (temp_fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
case SND_SOC_DAIFMT_NB_NF:
|
|
/*
|
|
* Normal BCLK + FS.
|
|
* FS active low. TX data driven on falling edge of bit clock
|
|
* and RX data sampled on rising edge of bit clock.
|
|
*/
|
|
regs->pcr0 |= FSXP | FSRP |
|
|
CLKXP | CLKRP;
|
|
break;
|
|
case SND_SOC_DAIFMT_NB_IF:
|
|
regs->pcr0 |= CLKXP | CLKRP;
|
|
break;
|
|
case SND_SOC_DAIFMT_IB_NF:
|
|
regs->pcr0 |= FSXP | FSRP;
|
|
break;
|
|
case SND_SOC_DAIFMT_IB_IF:
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int omap_mcbsp_dai_set_clkdiv(struct snd_soc_dai *cpu_dai,
|
|
int div_id, int div)
|
|
{
|
|
struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
|
|
|
|
if (div_id != OMAP_MCBSP_CLKGDV)
|
|
return -ENODEV;
|
|
|
|
mcbsp_data->clk_div = div;
|
|
regs->srgr1 |= CLKGDV(div - 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int omap_mcbsp_dai_set_clks_src(struct omap_mcbsp_data *mcbsp_data,
|
|
int clk_id)
|
|
{
|
|
int sel_bit;
|
|
u16 reg, reg_devconf1 = OMAP243X_CONTROL_DEVCONF1;
|
|
|
|
if (cpu_class_is_omap1()) {
|
|
/* OMAP1's can use only external source clock */
|
|
if (unlikely(clk_id == OMAP_MCBSP_SYSCLK_CLKS_FCLK))
|
|
return -EINVAL;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
if (cpu_is_omap2420() && mcbsp_data->bus_id > 1)
|
|
return -EINVAL;
|
|
|
|
if (cpu_is_omap343x())
|
|
reg_devconf1 = OMAP343X_CONTROL_DEVCONF1;
|
|
|
|
switch (mcbsp_data->bus_id) {
|
|
case 0:
|
|
reg = OMAP2_CONTROL_DEVCONF0;
|
|
sel_bit = 2;
|
|
break;
|
|
case 1:
|
|
reg = OMAP2_CONTROL_DEVCONF0;
|
|
sel_bit = 6;
|
|
break;
|
|
case 2:
|
|
reg = reg_devconf1;
|
|
sel_bit = 0;
|
|
break;
|
|
case 3:
|
|
reg = reg_devconf1;
|
|
sel_bit = 2;
|
|
break;
|
|
case 4:
|
|
reg = reg_devconf1;
|
|
sel_bit = 4;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (clk_id == OMAP_MCBSP_SYSCLK_CLKS_FCLK)
|
|
omap_ctrl_writel(omap_ctrl_readl(reg) & ~(1 << sel_bit), reg);
|
|
else
|
|
omap_ctrl_writel(omap_ctrl_readl(reg) | (1 << sel_bit), reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int omap_mcbsp_dai_set_rcvr_src(struct omap_mcbsp_data *mcbsp_data,
|
|
int clk_id)
|
|
{
|
|
int sel_bit, set = 0;
|
|
u16 reg = OMAP2_CONTROL_DEVCONF0;
|
|
|
|
if (cpu_class_is_omap1())
|
|
return -EINVAL; /* TODO: Can this be implemented for OMAP1? */
|
|
if (mcbsp_data->bus_id != 0)
|
|
return -EINVAL;
|
|
|
|
switch (clk_id) {
|
|
case OMAP_MCBSP_CLKR_SRC_CLKX:
|
|
set = 1;
|
|
case OMAP_MCBSP_CLKR_SRC_CLKR:
|
|
sel_bit = 3;
|
|
break;
|
|
case OMAP_MCBSP_FSR_SRC_FSX:
|
|
set = 1;
|
|
case OMAP_MCBSP_FSR_SRC_FSR:
|
|
sel_bit = 4;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (set)
|
|
omap_ctrl_writel(omap_ctrl_readl(reg) | (1 << sel_bit), reg);
|
|
else
|
|
omap_ctrl_writel(omap_ctrl_readl(reg) & ~(1 << sel_bit), reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
|
|
int clk_id, unsigned int freq,
|
|
int dir)
|
|
{
|
|
struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
|
|
int err = 0;
|
|
|
|
mcbsp_data->in_freq = freq;
|
|
|
|
switch (clk_id) {
|
|
case OMAP_MCBSP_SYSCLK_CLK:
|
|
regs->srgr2 |= CLKSM;
|
|
break;
|
|
case OMAP_MCBSP_SYSCLK_CLKS_FCLK:
|
|
case OMAP_MCBSP_SYSCLK_CLKS_EXT:
|
|
err = omap_mcbsp_dai_set_clks_src(mcbsp_data, clk_id);
|
|
break;
|
|
|
|
case OMAP_MCBSP_SYSCLK_CLKX_EXT:
|
|
regs->srgr2 |= CLKSM;
|
|
case OMAP_MCBSP_SYSCLK_CLKR_EXT:
|
|
regs->pcr0 |= SCLKME;
|
|
break;
|
|
|
|
case OMAP_MCBSP_CLKR_SRC_CLKR:
|
|
case OMAP_MCBSP_CLKR_SRC_CLKX:
|
|
case OMAP_MCBSP_FSR_SRC_FSR:
|
|
case OMAP_MCBSP_FSR_SRC_FSX:
|
|
err = omap_mcbsp_dai_set_rcvr_src(mcbsp_data, clk_id);
|
|
break;
|
|
default:
|
|
err = -ENODEV;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static struct snd_soc_dai_ops mcbsp_dai_ops = {
|
|
.startup = omap_mcbsp_dai_startup,
|
|
.shutdown = omap_mcbsp_dai_shutdown,
|
|
.trigger = omap_mcbsp_dai_trigger,
|
|
.delay = omap_mcbsp_dai_delay,
|
|
.hw_params = omap_mcbsp_dai_hw_params,
|
|
.set_fmt = omap_mcbsp_dai_set_dai_fmt,
|
|
.set_clkdiv = omap_mcbsp_dai_set_clkdiv,
|
|
.set_sysclk = omap_mcbsp_dai_set_dai_sysclk,
|
|
};
|
|
|
|
static int mcbsp_dai_probe(struct snd_soc_dai *dai)
|
|
{
|
|
mcbsp_data[dai->id].bus_id = dai->id;
|
|
snd_soc_dai_set_drvdata(dai, &mcbsp_data[dai->id].bus_id);
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_soc_dai_driver omap_mcbsp_dai =
|
|
{
|
|
.probe = mcbsp_dai_probe,
|
|
.playback = {
|
|
.channels_min = 1,
|
|
.channels_max = 16,
|
|
.rates = OMAP_MCBSP_RATES,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
|
|
},
|
|
.capture = {
|
|
.channels_min = 1,
|
|
.channels_max = 16,
|
|
.rates = OMAP_MCBSP_RATES,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
|
|
},
|
|
.ops = &mcbsp_dai_ops,
|
|
};
|
|
|
|
int omap_mcbsp_st_info_volsw(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
int max = mc->max;
|
|
int min = mc->min;
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
uinfo->count = 1;
|
|
uinfo->value.integer.min = min;
|
|
uinfo->value.integer.max = max;
|
|
return 0;
|
|
}
|
|
|
|
#define OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(id, channel) \
|
|
static int \
|
|
omap_mcbsp##id##_set_st_ch##channel##_volume(struct snd_kcontrol *kc, \
|
|
struct snd_ctl_elem_value *uc) \
|
|
{ \
|
|
struct soc_mixer_control *mc = \
|
|
(struct soc_mixer_control *)kc->private_value; \
|
|
int max = mc->max; \
|
|
int min = mc->min; \
|
|
int val = uc->value.integer.value[0]; \
|
|
\
|
|
if (val < min || val > max) \
|
|
return -EINVAL; \
|
|
\
|
|
/* OMAP McBSP implementation uses index values 0..4 */ \
|
|
return omap_st_set_chgain((id)-1, channel, val); \
|
|
}
|
|
|
|
#define OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(id, channel) \
|
|
static int \
|
|
omap_mcbsp##id##_get_st_ch##channel##_volume(struct snd_kcontrol *kc, \
|
|
struct snd_ctl_elem_value *uc) \
|
|
{ \
|
|
s16 chgain; \
|
|
\
|
|
if (omap_st_get_chgain((id)-1, channel, &chgain)) \
|
|
return -EAGAIN; \
|
|
\
|
|
uc->value.integer.value[0] = chgain; \
|
|
return 0; \
|
|
}
|
|
|
|
OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(2, 0)
|
|
OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(2, 1)
|
|
OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(3, 0)
|
|
OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(3, 1)
|
|
OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(2, 0)
|
|
OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(2, 1)
|
|
OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(3, 0)
|
|
OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(3, 1)
|
|
|
|
static int omap_mcbsp_st_put_mode(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
u8 value = ucontrol->value.integer.value[0];
|
|
|
|
if (value == omap_st_is_enabled(mc->reg))
|
|
return 0;
|
|
|
|
if (value)
|
|
omap_st_enable(mc->reg);
|
|
else
|
|
omap_st_disable(mc->reg);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int omap_mcbsp_st_get_mode(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
|
|
ucontrol->value.integer.value[0] = omap_st_is_enabled(mc->reg);
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_kcontrol_new omap_mcbsp2_st_controls[] = {
|
|
SOC_SINGLE_EXT("McBSP2 Sidetone Switch", 1, 0, 1, 0,
|
|
omap_mcbsp_st_get_mode, omap_mcbsp_st_put_mode),
|
|
OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP2 Sidetone Channel 0 Volume",
|
|
-32768, 32767,
|
|
omap_mcbsp2_get_st_ch0_volume,
|
|
omap_mcbsp2_set_st_ch0_volume),
|
|
OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP2 Sidetone Channel 1 Volume",
|
|
-32768, 32767,
|
|
omap_mcbsp2_get_st_ch1_volume,
|
|
omap_mcbsp2_set_st_ch1_volume),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new omap_mcbsp3_st_controls[] = {
|
|
SOC_SINGLE_EXT("McBSP3 Sidetone Switch", 2, 0, 1, 0,
|
|
omap_mcbsp_st_get_mode, omap_mcbsp_st_put_mode),
|
|
OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP3 Sidetone Channel 0 Volume",
|
|
-32768, 32767,
|
|
omap_mcbsp3_get_st_ch0_volume,
|
|
omap_mcbsp3_set_st_ch0_volume),
|
|
OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP3 Sidetone Channel 1 Volume",
|
|
-32768, 32767,
|
|
omap_mcbsp3_get_st_ch1_volume,
|
|
omap_mcbsp3_set_st_ch1_volume),
|
|
};
|
|
|
|
int omap_mcbsp_st_add_controls(struct snd_soc_codec *codec, int mcbsp_id)
|
|
{
|
|
if (!cpu_is_omap34xx())
|
|
return -ENODEV;
|
|
|
|
switch (mcbsp_id) {
|
|
case 1: /* McBSP 2 */
|
|
return snd_soc_add_controls(codec, omap_mcbsp2_st_controls,
|
|
ARRAY_SIZE(omap_mcbsp2_st_controls));
|
|
case 2: /* McBSP 3 */
|
|
return snd_soc_add_controls(codec, omap_mcbsp3_st_controls,
|
|
ARRAY_SIZE(omap_mcbsp3_st_controls));
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(omap_mcbsp_st_add_controls);
|
|
|
|
static __devinit int asoc_mcbsp_probe(struct platform_device *pdev)
|
|
{
|
|
return snd_soc_register_dai(&pdev->dev, &omap_mcbsp_dai);
|
|
}
|
|
|
|
static int __devexit asoc_mcbsp_remove(struct platform_device *pdev)
|
|
{
|
|
snd_soc_unregister_dai(&pdev->dev);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver asoc_mcbsp_driver = {
|
|
.driver = {
|
|
.name = "omap-mcbsp-dai",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
|
|
.probe = asoc_mcbsp_probe,
|
|
.remove = __devexit_p(asoc_mcbsp_remove),
|
|
};
|
|
|
|
static int __init snd_omap_mcbsp_init(void)
|
|
{
|
|
return platform_driver_register(&asoc_mcbsp_driver);
|
|
}
|
|
module_init(snd_omap_mcbsp_init);
|
|
|
|
static void __exit snd_omap_mcbsp_exit(void)
|
|
{
|
|
platform_driver_unregister(&asoc_mcbsp_driver);
|
|
}
|
|
module_exit(snd_omap_mcbsp_exit);
|
|
|
|
MODULE_AUTHOR("Jarkko Nikula <jhnikula@gmail.com>");
|
|
MODULE_DESCRIPTION("OMAP I2S SoC Interface");
|
|
MODULE_LICENSE("GPL");
|