Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1fb34b48 authored by Maxime Ripard's avatar Maxime Ripard Committed by Mark Brown
Browse files

ASoC: sun4i: Implement MIC1 capture



One of the input path used in the Allwinner codec is the MIC1. Add support
for it.

Signed-off-by: default avatarMaxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 8005c49d
Loading
Loading
Loading
Loading
+197 −31
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@

/* Codec ADC register offsets and bit fields */
#define SUN4I_CODEC_ADC_FIFOC			(0x1c)
#define SUN4I_CODEC_ADC_FIFOC_ADC_FS			(29)
#define SUN4I_CODEC_ADC_FIFOC_EN_AD			(28)
#define SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE		(24)
#define SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL		(8)
@@ -102,6 +103,7 @@ struct sun4i_codec {
	struct clk	*clk_apb;
	struct clk	*clk_module;

	struct snd_dmaengine_dai_dma_data	capture_dma_data;
	struct snd_dmaengine_dai_dma_data	playback_dma_data;
};

@@ -136,26 +138,54 @@ static void sun4i_codec_stop_playback(struct sun4i_codec *scodec)
			   0);
}

static void sun4i_codec_start_capture(struct sun4i_codec *scodec)
{
	/*
	 * FIXME: according to the BSP, we might need to drive a PA
	 *        GPIO high here on some boards
	 */

	/* Enable ADC DRQ */
	regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
			   BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN),
			   BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN));
}

static void sun4i_codec_stop_capture(struct sun4i_codec *scodec)
{
	/*
	 * FIXME: according to the BSP, we might need to drive a PA
	 *        GPIO low here on some boards
	 */

	/* Disable ADC DRQ */
	regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
			   BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN), 0);
}

static int sun4i_codec_trigger(struct snd_pcm_substream *substream, int cmd,
			       struct snd_soc_dai *dai)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card);

	if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
		return -ENOTSUPP;

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
			sun4i_codec_start_playback(scodec);
		else
			sun4i_codec_start_capture(scodec);
		break;

	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
			sun4i_codec_stop_playback(scodec);
		else
			sun4i_codec_stop_capture(scodec);
		break;

	default:
@@ -165,15 +195,54 @@ static int sun4i_codec_trigger(struct snd_pcm_substream *substream, int cmd,
	return 0;
}

static int sun4i_codec_prepare(struct snd_pcm_substream *substream,
static int sun4i_codec_prepare_capture(struct snd_pcm_substream *substream,
				       struct snd_soc_dai *dai)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card);
	u32 val;

	if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
		return -ENOTSUPP;

	/* Flush RX FIFO */
	regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
			   BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH),
			   BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH));


	/* Set RX FIFO trigger level */
	regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
			   0xf << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL,
			   0x7 << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL);

	/*
	 * FIXME: Undocumented in the datasheet, but
	 *        Allwinner's code mentions that it is related
	 *        related to microphone gain
	 */
	regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_ACTL,
			   0x3 << 25,
			   0x1 << 25);

	if (of_device_is_compatible(scodec->dev->of_node,
				    "allwinner,sun7i-a20-codec"))
		/* FIXME: Undocumented bits */
		regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_TUNE,
				   0x3 << 8,
				   0x1 << 8);

	/* Fill most significant bits with valid data MSB */
	regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
			   BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE),
			   BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE));

	return 0;
}

static int sun4i_codec_prepare_playback(struct snd_pcm_substream *substream,
					struct snd_soc_dai *dai)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card);
	u32 val;

	/* Flush the TX FIFO */
	regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
@@ -202,6 +271,15 @@ static int sun4i_codec_prepare(struct snd_pcm_substream *substream,
			   0);

	return 0;
};

static int sun4i_codec_prepare(struct snd_pcm_substream *substream,
			       struct snd_soc_dai *dai)
{
	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		return sun4i_codec_prepare_playback(substream, dai);

	return sun4i_codec_prepare_capture(substream, dai);
}

static unsigned long sun4i_codec_get_mod_freq(struct snd_pcm_hw_params *params)
@@ -276,30 +354,34 @@ static int sun4i_codec_get_hw_rate(struct snd_pcm_hw_params *params)
	}
}

static int sun4i_codec_hw_params(struct snd_pcm_substream *substream,
static int sun4i_codec_hw_params_capture(struct sun4i_codec *scodec,
					 struct snd_pcm_hw_params *params,
				 struct snd_soc_dai *dai)
					 unsigned int hwrate)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card);
	unsigned long clk_freq;
	int ret, hwrate;
	u32 val;

	if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
		return -ENOTSUPP;
	/* Set ADC sample rate */
	regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
			   7 << SUN4I_CODEC_ADC_FIFOC_ADC_FS,
			   hwrate << SUN4I_CODEC_ADC_FIFOC_ADC_FS);

	clk_freq = sun4i_codec_get_mod_freq(params);
	if (!clk_freq)
		return -EINVAL;
	/* Set the number of channels we want to use */
	if (params_channels(params) == 1)
		regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
				   BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN),
				   BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN));
	else
		regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
				   BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN), 0);

	ret = clk_set_rate(scodec->clk_module, clk_freq);
	if (ret)
		return ret;
	return 0;
}

	hwrate = sun4i_codec_get_hw_rate(params);
	if (hwrate < 0)
		return hwrate;
static int sun4i_codec_hw_params_playback(struct sun4i_codec *scodec,
					  struct snd_pcm_hw_params *params,
					  unsigned int hwrate)
{
	u32 val;

	/* Set DAC sample rate */
	regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
@@ -344,6 +426,34 @@ static int sun4i_codec_hw_params(struct snd_pcm_substream *substream,
	return 0;
}

static int sun4i_codec_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 sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card);
	unsigned long clk_freq;
	int hwrate;

	clk_freq = sun4i_codec_get_mod_freq(params);
	if (!clk_freq)
		return -EINVAL;

	if (clk_set_rate(scodec->clk_module, clk_freq))
		return -EINVAL;

	hwrate = sun4i_codec_get_hw_rate(params);
	if (hwrate < 0)
		return hwrate;

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		return sun4i_codec_hw_params_playback(scodec, params,
						      hwrate);

	return sun4i_codec_hw_params_capture(scodec, params,
					     hwrate);
}

static int sun4i_codec_startup(struct snd_pcm_substream *substream,
			       struct snd_soc_dai *dai)
{
@@ -394,6 +504,20 @@ static struct snd_soc_dai_driver sun4i_codec_dai = {
				  SNDRV_PCM_FMTBIT_S32_LE,
		.sig_bits	= 24,
	},
	.capture = {
		.stream_name	= "Codec Capture",
		.channels_min	= 1,
		.channels_max	= 2,
		.rate_min	= 8000,
		.rate_max	= 192000,
		.rates		= SNDRV_PCM_RATE_8000_48000 |
				  SNDRV_PCM_RATE_96000 |
				  SNDRV_PCM_RATE_192000 |
				  SNDRV_PCM_RATE_KNOT,
		.formats	= SNDRV_PCM_FMTBIT_S16_LE |
				  SNDRV_PCM_FMTBIT_S32_LE,
		.sig_bits	= 24,
	},
};

/*** Codec ***/
@@ -429,11 +553,22 @@ static const struct snd_kcontrol_new sun4i_codec_pa_mixer_controls[] = {
};

static const struct snd_soc_dapm_widget sun4i_codec_dapm_widgets[] = {
	/* Digital parts of the ADCs */
	SND_SOC_DAPM_SUPPLY("ADC", SUN4I_CODEC_ADC_FIFOC,
			    SUN4I_CODEC_ADC_FIFOC_EN_AD, 0,
			    NULL, 0),

	/* Digital parts of the DACs */
	SND_SOC_DAPM_SUPPLY("DAC", SUN4I_CODEC_DAC_DPC,
			    SUN4I_CODEC_DAC_DPC_EN_DA, 0,
			    NULL, 0),

	/* Analog parts of the ADCs */
	SND_SOC_DAPM_ADC("Left ADC", "Codec Capture", SUN4I_CODEC_ADC_ACTL,
			 SUN4I_CODEC_ADC_ACTL_ADC_L_EN, 0),
	SND_SOC_DAPM_ADC("Right ADC", "Codec Capture", SUN4I_CODEC_ADC_ACTL,
			 SUN4I_CODEC_ADC_ACTL_ADC_R_EN, 0),

	/* Analog parts of the DACs */
	SND_SOC_DAPM_DAC("Left DAC", "Codec Playback", SUN4I_CODEC_DAC_ACTL,
			 SUN4I_CODEC_DAC_ACTL_DACAENL, 0),
@@ -452,6 +587,14 @@ static const struct snd_soc_dapm_widget sun4i_codec_dapm_widgets[] = {
	SND_SOC_DAPM_SUPPLY("Mixer Enable", SUN4I_CODEC_DAC_ACTL,
			    SUN4I_CODEC_DAC_ACTL_MIXEN, 0, NULL, 0),

	/* VMIC */
	SND_SOC_DAPM_SUPPLY("VMIC", SUN4I_CODEC_ADC_ACTL,
			    SUN4I_CODEC_ADC_ACTL_VMICEN, 0, NULL, 0),

	/* Mic Pre-Amplifiers */
	SND_SOC_DAPM_PGA("MIC1 Pre-Amplifier", SUN4I_CODEC_ADC_ACTL,
			 SUN4I_CODEC_ADC_ACTL_PREG1EN, 0, NULL, 0),

	/* Pre-Amplifier */
	SND_SOC_DAPM_MIXER("Pre-Amplifier", SUN4I_CODEC_ADC_ACTL,
			   SUN4I_CODEC_ADC_ACTL_PA_EN, 0,
@@ -460,15 +603,19 @@ static const struct snd_soc_dapm_widget sun4i_codec_dapm_widgets[] = {
	SND_SOC_DAPM_SWITCH("Pre-Amplifier Mute", SND_SOC_NOPM, 0, 0,
			    &sun4i_codec_pa_mute),

	SND_SOC_DAPM_INPUT("Mic1"),

	SND_SOC_DAPM_OUTPUT("HP Right"),
	SND_SOC_DAPM_OUTPUT("HP Left"),
};

static const struct snd_soc_dapm_route sun4i_codec_dapm_routes[] = {
	/* Left DAC Routes */
	/* Left ADC / DAC Routes */
	{ "Left ADC", NULL, "ADC" },
	{ "Left DAC", NULL, "DAC" },

	/* Right DAC Routes */
	/* Right ADC / DAC Routes */
	{ "Right ADC", NULL, "ADC" },
	{ "Right DAC", NULL, "DAC" },

	/* Right Mixer Routes */
@@ -490,6 +637,12 @@ static const struct snd_soc_dapm_route sun4i_codec_dapm_routes[] = {
	{ "Pre-Amplifier Mute", "Switch", "Pre-Amplifier" },
	{ "HP Right", NULL, "Pre-Amplifier Mute" },
	{ "HP Left", NULL, "Pre-Amplifier Mute" },

	/* Mic1 Routes */
	{ "Left ADC", NULL, "MIC1 Pre-Amplifier" },
	{ "Right ADC", NULL, "MIC1 Pre-Amplifier" },
	{ "MIC1 Pre-Amplifier", NULL, "Mic1"},
	{ "Mic1", NULL, "VMIC" },
};

static struct snd_soc_codec_driver sun4i_codec_codec = {
@@ -515,7 +668,7 @@ static int sun4i_codec_dai_probe(struct snd_soc_dai *dai)
	struct sun4i_codec *scodec = snd_soc_card_get_drvdata(card);

	snd_soc_dai_init_dma_data(dai, &scodec->playback_dma_data,
				  NULL);
				  &scodec->capture_dma_data);

	return 0;
}
@@ -531,6 +684,14 @@ static struct snd_soc_dai_driver dummy_cpu_dai = {
		.formats	= SUN4I_CODEC_FORMATS,
		.sig_bits	= 24,
	},
	.capture = {
		.stream_name	= "Capture",
		.channels_min	= 1,
		.channels_max	= 2,
		.rates 		= SUN4I_CODEC_RATES,
		.formats 	= SUN4I_CODEC_FORMATS,
		.sig_bits	= 24,
	 },
};

static const struct regmap_config sun4i_codec_regmap_config = {
@@ -638,6 +799,11 @@ static int sun4i_codec_probe(struct platform_device *pdev)
	scodec->playback_dma_data.maxburst = 4;
	scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;

	/* DMA configuration for RX FIFO */
	scodec->capture_dma_data.addr = res->start + SUN4I_CODEC_ADC_RXDATA;
	scodec->capture_dma_data.maxburst = 4;
	scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;

	ret = snd_soc_register_codec(&pdev->dev, &sun4i_codec_codec,
				     &sun4i_codec_dai, 1);
	if (ret) {