[alsa-devel] [PATCH - BT and FM audio for zoom2 1/2] Add clock-only codec to provide McBSP clock source.

sean.mcneil at ti.com sean.mcneil at ti.com
Mon Aug 3 06:29:14 CEST 2009


From: Sean McNeil <sean.mcneil at ti.com>


Signed-off-by: Sean McNeil <sean.mcneil at ti.com>

diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c
index 818fb37..c343ceb 100644
--- a/sound/soc/codecs/twl4030.c
+++ b/sound/soc/codecs/twl4030.c
@@ -56,7 +56,7 @@ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = {
 	0x00, /* REG_AVTXL2PGA		(0xC)	*/
 	0x00, /* REG_AVTXR2PGA		(0xD)	*/
 	0x01, /* REG_AUDIO_IF		(0xE)	*/
-	0x00, /* REG_VOICE_IF		(0xF)	*/
+	0x04, /* REG_VOICE_IF		(0xF)	*/
 	0x00, /* REG_ARXR1PGA		(0x10)	*/
 	0x00, /* REG_ARXL1PGA		(0x11)	*/
 	0x6c, /* REG_ARXR2PGA		(0x12)	*/
@@ -118,25 +118,32 @@ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = {
 	0x00, /* REG_SW_SHADOW		(0x4A)	- Shadow, non HW register */
 };
 
+struct substream_item {
+	struct list_head started;
+	struct list_head configured;
+	struct snd_pcm_substream *substream;
+	int use256FS;
+};
+
 /* codec private data */
 struct twl4030_priv {
+	struct mutex mutex;
+
+	unsigned int extClock;
 	unsigned int bypass_state;
 	unsigned int codec_powered;
 	unsigned int codec_muted;
 
-	struct snd_pcm_substream *master_substream;
-	struct snd_pcm_substream *slave_substream;
-
-	unsigned int configured;
-	unsigned int rate;
-	unsigned int sample_bits;
-	unsigned int channels;
+	struct list_head started_list;
+	struct list_head config_list;
 
 	unsigned int sysclk;
 
 	/* Headset output state handling */
 	unsigned int hsl_enabled;
 	unsigned int hsr_enabled;
+
+	struct snd_pcm_hw_params params;
 };
 
 /*
@@ -966,7 +973,7 @@ static int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol,
 	unsigned short val;
 	unsigned short mask, bitmask;
 
-	if (twl4030->configured) {
+	if (!list_empty(&twl4030->config_list)) {
 		printk(KERN_ERR "twl4030 operation mode cannot be "
 			"changed on-the-fly\n");
 		return -EBUSY;
@@ -1534,34 +1541,68 @@ static int twl4030_set_bias_level(struct snd_soc_codec *codec,
 	return 0;
 }
 
-static void twl4030_constraints(struct twl4030_priv *twl4030,
-				struct snd_pcm_substream *mst_substream)
+static unsigned int twl4030_rate_min(struct substream_item *item,
+	unsigned int rate)
 {
-	struct snd_pcm_substream *slv_substream;
-
-	/* Pick the stream, which need to be constrained */
-	if (mst_substream == twl4030->master_substream)
-		slv_substream = twl4030->slave_substream;
-	else if (mst_substream == twl4030->slave_substream)
-		slv_substream = twl4030->master_substream;
-	else /* This should not happen.. */
-		return;
+	static const unsigned int table[] = {
+			8000, 11025, 12000, 16000, 22050,
+			24000, 32000, 44100, 48000, 96000};
+	unsigned int value = rate;
+
+	if (item->use256FS) {
+		int i;
+		rate *= 256;
+		for (i = 0; i < ARRAY_SIZE(table); i++)
+			if (rate % table[i] == 0) {
+				value = table[i];
+				break;
+			}
+	}
+	return value;
+}
 
-	/* Set the constraints according to the already configured stream */
-	snd_pcm_hw_constraint_minmax(slv_substream->runtime,
-				SNDRV_PCM_HW_PARAM_RATE,
-				twl4030->rate,
-				twl4030->rate);
-
-	snd_pcm_hw_constraint_minmax(slv_substream->runtime,
-				SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
-				twl4030->sample_bits,
-				twl4030->sample_bits);
-
-	snd_pcm_hw_constraint_minmax(slv_substream->runtime,
-				SNDRV_PCM_HW_PARAM_CHANNELS,
-				twl4030->channels,
-				twl4030->channels);
+static unsigned int twl4030_rate_max(struct substream_item *item,
+	unsigned int rate)
+{
+	static const unsigned int table[] = {
+			96000, 48000, 44100, 32000, 24000,
+			22050, 16000, 12000, 11025, 8000};
+	unsigned int value = rate;
+
+	if (item->use256FS) {
+		int i;
+		rate *= 256;
+		for (i = 0; i < ARRAY_SIZE(table); i++)
+			if (rate % table[i] == 0) {
+				value = table[i];
+				break;
+			}
+	}
+	return value;
+}
+
+static void twl4030_constraints(struct twl4030_priv *twl4030)
+{
+	struct substream_item *item;
+	unsigned int value;
+
+	list_for_each_entry(item, &twl4030->started_list, started) {
+
+		/* Set constraints according to the already configured stream */
+		value = params_rate(&twl4030->params);
+		if (value)
+			snd_pcm_hw_constraint_minmax(item->substream->runtime,
+					SNDRV_PCM_HW_PARAM_RATE,
+					twl4030_rate_min(item, value),
+					twl4030_rate_max(item, value));
+
+		value = hw_param_interval(&twl4030->params,
+					SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
+		if (value && !item->use256FS)
+			snd_pcm_hw_constraint_minmax(item->substream->runtime,
+					SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+					value, value);
+	}
 }
 
 /* In case of 4 channel mode, the RX1 L/R for playback and the TX2 L/R for
@@ -1586,6 +1627,53 @@ static void twl4030_tdm_enable(struct snd_soc_codec *codec, int direction,
 	twl4030_write(codec, TWL4030_REG_OPTION, reg);
 }
 
+static int twl4030_new_substream(struct twl4030_priv *twl4030,
+		struct snd_pcm_substream *substream, int use256FS)
+{
+	struct substream_item *item;
+
+	item = kzalloc(sizeof(struct snd_pcm_substream), GFP_KERNEL);
+	if (!item)
+		return -ENOMEM;
+
+	item->substream = substream;
+	item->use256FS = use256FS;
+
+	mutex_lock(&twl4030->mutex);
+	list_add_tail(&item->started, &twl4030->started_list);
+	twl4030->extClock += item->use256FS;
+	mutex_unlock(&twl4030->mutex);
+
+	return 0;
+}
+
+static void twl4030_del_substream(struct twl4030_priv *twl4030,
+		struct snd_pcm_substream *substream)
+{
+	struct substream_item *item;
+
+	mutex_lock(&twl4030->mutex);
+
+	list_for_each_entry(item, &twl4030->config_list, configured) {
+		if (item->substream == substream) {
+			printk(KERN_ERR "TWL4030 deleted substream still configured!\n");
+			list_del(&item->configured);
+			break;
+		}
+	}
+
+	list_for_each_entry(item, &twl4030->started_list, started) {
+		if (item->substream == substream) {
+			list_del(&item->started);
+			twl4030->extClock -= item->use256FS;
+			kfree(item);
+			break;
+		}
+	}
+
+	mutex_unlock(&twl4030->mutex);
+}
+
 static int twl4030_startup(struct snd_pcm_substream *substream,
 			   struct snd_soc_dai *dai)
 {
@@ -1594,82 +1682,45 @@ static int twl4030_startup(struct snd_pcm_substream *substream,
 	struct snd_soc_codec *codec = socdev->card->codec;
 	struct twl4030_priv *twl4030 = codec->private_data;
 
-	if (twl4030->master_substream) {
-		twl4030->slave_substream = substream;
-		/* The DAI has one configuration for playback and capture, so
-		 * if the DAI has been already configured then constrain this
-		 * substream to match it. */
-		if (twl4030->configured)
-			twl4030_constraints(twl4030, twl4030->master_substream);
-	} else {
-		if (!(twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) &
-			TWL4030_OPTION_1)) {
-			/* In option2 4 channel is not supported, set the
-			 * constraint for the first stream for channels, the
-			 * second stream will 'inherit' this cosntraint */
-			snd_pcm_hw_constraint_minmax(substream->runtime,
-						SNDRV_PCM_HW_PARAM_CHANNELS,
-						2, 2);
-		}
-		twl4030->master_substream = substream;
+	if (!(twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) &
+		TWL4030_OPTION_1)) {
+		/* In option2 4 channel is not supported, set the
+		 * constraint for the first stream for channels, the
+		 * second stream will 'inherit' this cosntraint */
+		snd_pcm_hw_constraint_minmax(substream->runtime,
+					SNDRV_PCM_HW_PARAM_CHANNELS,
+					2, 2);
 	}
 
-	return 0;
+	return twl4030_new_substream(twl4030, substream, 0);
 }
 
 static void twl4030_shutdown(struct snd_pcm_substream *substream,
-			     struct snd_soc_dai *dai)
+		   struct snd_soc_dai *dai)
 {
 	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;
 
-	if (twl4030->master_substream == substream)
-		twl4030->master_substream = twl4030->slave_substream;
-
-	twl4030->slave_substream = NULL;
-
-	/* If all streams are closed, or the remaining stream has not yet
-	 * been configured than set the DAI as not configured. */
-	if (!twl4030->master_substream)
-		twl4030->configured = 0;
-	 else if (!twl4030->master_substream->runtime->channels)
-		twl4030->configured = 0;
+	twl4030_del_substream(twl4030, substream);
 
 	 /* If the closing substream had 4 channel, do the necessary cleanup */
 	if (substream->runtime->channels == 4)
 		twl4030_tdm_enable(codec, substream->stream, 0);
 }
 
-static int twl4030_hw_params(struct snd_pcm_substream *substream,
-			   struct snd_pcm_hw_params *params,
-			   struct snd_soc_dai *dai)
+int twl4030_set_rate(struct snd_soc_codec *codec,
+		   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->card->codec;
 	struct twl4030_priv *twl4030 = codec->private_data;
-	u8 mode, old_mode, format, old_format;
+	u8 mode, old_mode;
 
-	 /* If the substream has 4 channel, do the necessary setup */
-	if (params_channels(params) == 4) {
-		format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
-		mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE);
-
-		/* Safety check: are we in the correct operating mode and
-		 * the interface is in TDM mode? */
-		if ((mode & TWL4030_OPTION_1) &&
-		    ((format & TWL4030_AIF_FORMAT) == TWL4030_AIF_FORMAT_TDM))
-			twl4030_tdm_enable(codec, substream->stream, 1);
-		else
-			return -EINVAL;
+	if (params_rate(&twl4030->params) &&
+		params_rate(&twl4030->params) != params_rate(params)) {
+		return -EBUSY;
 	}
 
-	if (twl4030->configured)
-		/* Ignoring hw_params for already configured DAI */
-		return 0;
-
 	/* bit rate */
 	old_mode = twl4030_read_reg_cache(codec,
 			TWL4030_REG_CODEC_MODE) & ~TWL4030_CODECPDZ;
@@ -1712,6 +1763,8 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream,
 		return -EINVAL;
 	}
 
+	params_rate(&twl4030->params) = params_rate(params);
+
 	if (mode != old_mode) {
 		/* change rate and set CODECPDZ */
 		twl4030_codec_enable(codec, 0);
@@ -1719,10 +1772,51 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream,
 		twl4030_codec_enable(codec, 1);
 	}
 
+	return 0;
+}
+EXPORT_SYMBOL_GPL(twl4030_set_rate);
+
+int twl4030_get_clock_divisor(struct snd_soc_codec *codec,
+		   struct snd_pcm_hw_params *params)
+{
+	struct twl4030_priv *twl4030 = codec->private_data;
+	int clock, divisor;
+
+	clock = params_rate(&twl4030->params) * 256;
+	divisor = clock / params_rate(params);
+	divisor /= params_channels(params);
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_U8:
+	case SNDRV_PCM_FORMAT_S8:
+		divisor /= 8;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		divisor /= 16;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		divisor /= 24;
+		break;
+	default:
+		printk(KERN_ERR "TWL4030 get_clock_divisor: unknown format %d\n",
+				params_format(params));
+		return -EINVAL;
+	}
+
+	return divisor;
+}
+EXPORT_SYMBOL_GPL(twl4030_get_clock_divisor);
+
+static int twl4030_set_format(struct snd_soc_codec *codec,
+		   struct snd_pcm_hw_params *params)
+{
+	struct twl4030_priv *twl4030 = codec->private_data;
+	u8 format, old_format;
+
 	/* sample size */
 	old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
-	format = old_format;
-	format &= ~TWL4030_DATA_WIDTH;
+	format = old_format & ~TWL4030_DATA_WIDTH;
+
 	switch (params_format(params)) {
 	case SNDRV_PCM_FORMAT_S16_LE:
 		format |= TWL4030_DATA_WIDTH_16S_16W;
@@ -1736,31 +1830,96 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream,
 		return -EINVAL;
 	}
 
-	if (format != old_format) {
+	if (format == old_format)
+		return 0;
 
-		/* clear CODECPDZ before changing format (codec requirement) */
-		twl4030_codec_enable(codec, 0);
+	if (params_format(&twl4030->params) &&
+		params_format(&twl4030->params) != params_format(params))
+		return -EBUSY;
 
-		/* change format */
-		twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+	*hw_param_mask(&twl4030->params, SNDRV_PCM_HW_PARAM_FORMAT) =
+		*hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
 
-		/* set CODECPDZ afterwards */
-		twl4030_codec_enable(codec, 1);
+	/* clear CODECPDZ before changing format (codec requirement) */
+	twl4030_codec_enable(codec, 0);
+
+	/* change format */
+	twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+
+	/* set CODECPDZ afterwards */
+	twl4030_codec_enable(codec, 1);
+
+	return 0;
+}
+
+static int twl4030_hw_params(struct snd_pcm_substream *substream,
+			   struct snd_pcm_hw_params *params,
+			   struct snd_soc_dai *dai)
+{
+	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;
+	int rval;
+
+	mutex_lock(&twl4030->mutex);
+
+	 /* If the substream has 4 channel, do the necessary setup */
+	if (params_channels(params) == 4) {
+		/* Safety check: are we in the correct operating mode? */
+		if ((twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) &
+			TWL4030_OPTION_1)) {
+			twl4030_tdm_enable(codec, substream->stream, 1);
+		} else {
+			mutex_unlock(&twl4030->mutex);
+			return -EINVAL;
+		}
 	}
 
-	/* Store the important parameters for the DAI configuration and set
-	 * the DAI as configured */
-	twl4030->configured = 1;
-	twl4030->rate = params_rate(params);
-	twl4030->sample_bits = hw_param_interval(params,
-					SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
-	twl4030->channels = params_channels(params);
+	rval = twl4030_set_rate(codec, params);
+	if (rval < 0) {
+		mutex_unlock(&twl4030->mutex);
+		return rval;
+	}
 
-	/* If both playback and capture streams are open, and one of them
+	rval = twl4030_set_format(codec, params);
+	if (rval < 0) {
+		mutex_unlock(&twl4030->mutex);
+		return rval;
+	}
+
+	/* If any other streams are currently open, and one of them
 	 * is setting the hw parameters right now (since we are here), set
-	 * constraints to the other stream to match the current one. */
-	if (twl4030->slave_substream)
-		twl4030_constraints(twl4030, substream);
+	 * constraints to the other stream(s) to match the current one. */
+	twl4030_constraints(twl4030);
+
+	mutex_unlock(&twl4030->mutex);
+
+	return 0;
+}
+
+static int twl4030_hw_free(struct snd_pcm_substream *substream,
+			struct snd_soc_dai *dai)
+{
+	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;
+	struct substream_item *item;
+
+	mutex_lock(&twl4030->mutex);
+
+	list_for_each_entry(item, &twl4030->config_list, configured) {
+		if (item->substream == substream) {
+			list_del(&item->configured);
+			break;
+		}
+	}
+
+	if (list_empty(&twl4030->config_list))
+		memset(&twl4030->params, 0, sizeof(twl4030->params));
+
+	mutex_unlock(&twl4030->mutex);
 
 	return 0;
 }
@@ -1797,10 +1956,39 @@ static int twl4030_set_dai_sysclk(struct snd_soc_dai *codec_dai,
 	return 0;
 }
 
+static int twl4030_set_ext_clock(struct snd_soc_codec *codec, int enable)
+{
+	u8 old_format, format;
+
+	/* get format */
+	old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
+
+	if (enable)
+		format = old_format | TWL4030_CLK256FS_EN;
+	else
+		format = old_format & ~TWL4030_CLK256FS_EN;
+
+	if (format != old_format) {
+
+		/* clear CODECPDZ before changing format (codec requirement) */
+		twl4030_codec_enable(codec, 0);
+
+		/* change format */
+		twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+
+		/* set CODECPDZ afterwards */
+		twl4030_codec_enable(codec, 1);
+	}
+
+	return 0;
+}
+
 static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai,
 			     unsigned int fmt)
 {
 	struct snd_soc_codec *codec = codec_dai->codec;
+	struct twl4030_priv *twl4030 = codec->private_data;
+	int use256FS = 0;
 	u8 old_format, format;
 
 	/* get format */
@@ -1811,11 +1999,10 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai,
 	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
 	case SND_SOC_DAIFMT_CBM_CFM:
 		format &= ~(TWL4030_AIF_SLAVE_EN);
-		format &= ~(TWL4030_CLK256FS_EN);
 		break;
 	case SND_SOC_DAIFMT_CBS_CFS:
 		format |= TWL4030_AIF_SLAVE_EN;
-		format |= TWL4030_CLK256FS_EN;
+		use256FS = 1;
 		break;
 	default:
 		return -EINVAL;
@@ -1846,7 +2033,7 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai,
 		twl4030_codec_enable(codec, 1);
 	}
 
-	return 0;
+	return twl4030_set_ext_clock(codec, use256FS | twl4030->extClock);
 }
 
 static int twl4030_set_tristate(struct snd_soc_dai *dai, int tristate)
@@ -1890,6 +2077,7 @@ static int twl4030_voice_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->card->codec;
+	struct twl4030_priv *twl4030 = codec->private_data;
 	u8 infreq;
 	u8 mode;
 
@@ -1917,7 +2105,7 @@ static int twl4030_voice_startup(struct snd_pcm_substream *substream,
 		return -EINVAL;
 	}
 
-	return 0;
+	return twl4030_new_substream(twl4030, substream, 1);
 }
 
 static void twl4030_voice_shutdown(struct snd_pcm_substream *substream,
@@ -1926,6 +2114,9 @@ static void twl4030_voice_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->card->codec;
+	struct twl4030_priv *twl4030 = codec->private_data;
+
+	twl4030_del_substream(twl4030, substream);
 
 	/* Enable voice digital filters */
 	twl4030_voice_enable(codec, substream->stream, 0);
@@ -1937,15 +2128,18 @@ static int twl4030_voice_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 old_mode, mode;
 
 	/* Enable voice digital filters */
 	twl4030_voice_enable(codec, substream->stream, 1);
 
+	mutex_lock(&twl4030->mutex);
+
 	/* bit rate */
-	old_mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE)
-		& ~(TWL4030_CODECPDZ);
-	mode = old_mode;
+	old_mode = twl4030_read_reg_cache(codec,
+			TWL4030_REG_CODEC_MODE) & ~TWL4030_CODECPDZ;
+	mode = old_mode & ~TWL4030_APLL_RATE;
 
 	switch (params_rate(params)) {
 	case 8000:
@@ -1955,6 +2149,7 @@ static int twl4030_voice_hw_params(struct snd_pcm_substream *substream,
 		mode |= TWL4030_SEL_16K;
 		break;
 	default:
+		mutex_unlock(&twl4030->mutex);
 		printk(KERN_ERR "TWL4030 voice hw params: unknown rate %d\n",
 			params_rate(params));
 		return -EINVAL;
@@ -1967,6 +2162,7 @@ static int twl4030_voice_hw_params(struct snd_pcm_substream *substream,
 		twl4030_codec_enable(codec, 1);
 	}
 
+	mutex_unlock(&twl4030->mutex);
 	return 0;
 }
 
@@ -1996,19 +2192,22 @@ static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai,
 		unsigned int fmt)
 {
 	struct snd_soc_codec *codec = codec_dai->codec;
+	struct twl4030_priv *twl4030 = codec->private_data;
+	int use256FS = 0;
 	u8 old_format, format;
 
 	/* get format */
 	old_format = twl4030_read_reg_cache(codec, TWL4030_REG_VOICE_IF);
-	format = old_format;
+	format = old_format & ~TWL4030_VIF_TRI_EN;
 
 	/* set master/slave audio interface */
 	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
-	case SND_SOC_DAIFMT_CBM_CFM:
+	case SND_SOC_DAIFMT_CBS_CFM:
 		format &= ~(TWL4030_VIF_SLAVE_EN);
 		break;
 	case SND_SOC_DAIFMT_CBS_CFS:
 		format |= TWL4030_VIF_SLAVE_EN;
+		use256FS = 1;
 		break;
 	default:
 		return -EINVAL;
@@ -2033,7 +2232,59 @@ static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai,
 		twl4030_codec_enable(codec, 1);
 	}
 
-	return 0;
+	return twl4030_set_ext_clock(codec, use256FS | twl4030->extClock);
+}
+
+static int twl4030_clock_startup(struct snd_pcm_substream *substream,
+		   struct snd_soc_dai *dai)
+{
+	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;
+
+	return twl4030_new_substream(twl4030, substream, 1);
+}
+
+static int twl4030_clock_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	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;
+	int rval;
+
+	mutex_lock(&twl4030->mutex);
+
+	rval = twl4030_set_rate(codec, params);
+
+	/* See if we are a multiple of the current FS. If so, then still OK. */
+	if (rval) {
+		int divisor = twl4030_get_clock_divisor(codec, params);
+		int clock = params_rate(&twl4030->params) * 256;
+		int remainder = clock % params_rate(params);
+
+		if (remainder == 0 && divisor <= 256)
+			rval = 0;
+	}
+
+	/* If any other streams are currently open, and one of them
+	 * is setting the hw parameters right now (since we are here), set
+	 * constraints to the other stream(s) to match the current one. */
+	twl4030_constraints(twl4030);
+
+	mutex_unlock(&twl4030->mutex);
+
+	return rval;
+}
+
+static int twl4030_clock_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+
+	return twl4030_set_ext_clock(codec, 1);
 }
 
 static int twl4030_voice_set_tristate(struct snd_soc_dai *dai, int tristate)
@@ -2056,6 +2307,7 @@ static struct snd_soc_dai_ops twl4030_dai_ops = {
 	.startup	= twl4030_startup,
 	.shutdown	= twl4030_shutdown,
 	.hw_params	= twl4030_hw_params,
+	.hw_free = twl4030_hw_free,
 	.set_sysclk	= twl4030_set_dai_sysclk,
 	.set_fmt	= twl4030_set_dai_fmt,
 	.set_tristate	= twl4030_set_tristate,
@@ -2065,26 +2317,38 @@ static struct snd_soc_dai_ops twl4030_dai_voice_ops = {
 	.startup	= twl4030_voice_startup,
 	.shutdown	= twl4030_voice_shutdown,
 	.hw_params	= twl4030_voice_hw_params,
+	.hw_free = twl4030_hw_free,
 	.set_sysclk	= twl4030_voice_set_dai_sysclk,
 	.set_fmt	= twl4030_voice_set_dai_fmt,
 	.set_tristate	= twl4030_voice_set_tristate,
 };
 
+static struct snd_soc_dai_ops twl4030_dai_clock_ops = {
+	.startup	= twl4030_clock_startup,
+	.shutdown	= twl4030_shutdown,
+	.hw_params	= twl4030_clock_hw_params,
+	.hw_free = twl4030_hw_free,
+	.set_sysclk = twl4030_set_dai_sysclk,
+	.set_fmt = twl4030_clock_set_dai_fmt,
+};
+
 struct snd_soc_dai twl4030_dai[] = {
 {
 	.name = "twl4030",
 	.playback = {
 		.stream_name = "HiFi Playback",
-		.channels_min = 2,
+		.channels_min = 1,
 		.channels_max = 4,
 		.rates = TWL4030_RATES | SNDRV_PCM_RATE_96000,
-		.formats = TWL4030_FORMATS,},
+		.formats = TWL4030_FORMATS,
+	},
 	.capture = {
 		.stream_name = "Capture",
-		.channels_min = 2,
+		.channels_min = 1,
 		.channels_max = 4,
 		.rates = TWL4030_RATES,
-		.formats = TWL4030_FORMATS,},
+		.formats = TWL4030_FORMATS,
+	},
 	.ops = &twl4030_dai_ops,
 },
 {
@@ -2092,17 +2356,37 @@ struct snd_soc_dai twl4030_dai[] = {
 	.playback = {
 		.stream_name = "Voice Playback",
 		.channels_min = 1,
-		.channels_max = 1,
+		.channels_max = 2,
 		.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
-		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
 	.capture = {
 		.stream_name = "Capture",
 		.channels_min = 1,
 		.channels_max = 2,
 		.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
-		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
 	.ops = &twl4030_dai_voice_ops,
 },
+{
+	.name = "twl4030 Clock",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = TWL4030_RATES,
+		.formats = SNDRV_PCM_FMTBIT_U8 | TWL4030_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = TWL4030_RATES,
+		.formats = SNDRV_PCM_FMTBIT_U8 | TWL4030_FORMATS,
+	},
+	.ops = &twl4030_dai_clock_ops,
+},
 };
 EXPORT_SYMBOL_GPL(twl4030_dai);
 
@@ -2220,6 +2504,9 @@ static int twl4030_probe(struct platform_device *pdev)
 		return -ENOMEM;
 	}
 
+	mutex_init(&twl4030->mutex);
+	INIT_LIST_HEAD(&twl4030->started_list);
+	INIT_LIST_HEAD(&twl4030->config_list);
 	codec->private_data = twl4030;
 	socdev->card->codec = codec;
 	mutex_init(&codec->mutex);
@@ -2257,13 +2544,13 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_twl4030);
 
 static int __init twl4030_modinit(void)
 {
-	return snd_soc_register_dais(&twl4030_dai[0], ARRAY_SIZE(twl4030_dai));
+	return snd_soc_register_dais(twl4030_dai, ARRAY_SIZE(twl4030_dai));
 }
 module_init(twl4030_modinit);
 
 static void __exit twl4030_exit(void)
 {
-	snd_soc_unregister_dais(&twl4030_dai[0], ARRAY_SIZE(twl4030_dai));
+	snd_soc_unregister_dais(twl4030_dai, ARRAY_SIZE(twl4030_dai));
 }
 module_exit(twl4030_exit);
 
diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h
index 2b4bfa2..fa23613 100644
--- a/sound/soc/codecs/twl4030.h
+++ b/sound/soc/codecs/twl4030.h
@@ -267,10 +267,15 @@
 
 #define TWL4030_DAI_HIFI		0
 #define TWL4030_DAI_VOICE		1
+#define TWL4030_DAI_CLOCK		2
 
-extern struct snd_soc_dai twl4030_dai[2];
+extern struct snd_soc_dai twl4030_dai[];
 extern struct snd_soc_codec_device soc_codec_dev_twl4030;
 
+extern int twl4030_set_rate(struct snd_soc_codec *, struct snd_pcm_hw_params *);
+extern int twl4030_get_clock_divisor(struct snd_soc_codec *,
+	struct snd_pcm_hw_params *);
+
 struct twl4030_setup_data {
 	unsigned int ramp_delay_value;
 	unsigned int sysclk;
-- 
1.6.0.4



More information about the Alsa-devel mailing list