Unverified Commit 91e99e11 authored by Maxime Ripard's avatar Maxime Ripard
Browse files

drm/vc4: hdmi: Register HDMI codec



The hdmi-codec brings a lot of advanced features, including the HDMI
channel mapping. Let's use it in our driver instead of our own codec.

Signed-off-by: default avatarMaxime Ripard <maxime@cerno.tech>
Reviewed-by: default avatarNicolas Saenz Julienne <nsaenz@kernel.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20210525132354.297468-11-maxime@cerno.tech
parent 8434111c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ config DRM_VC4
	select SND_PCM
	select SND_PCM_ELD
	select SND_SOC_GENERIC_DMAENGINE_PCM
	select SND_SOC_HDMI_CODEC
	select DRM_MIPI_DSI
	help
	  Choose this option if you have a system that has a Broadcom
+77 −165
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@
#include <linux/rational.h>
#include <linux/reset.h>
#include <sound/dmaengine_pcm.h>
#include <sound/hdmi-codec.h>
#include <sound/pcm_drm_eld.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
@@ -473,15 +474,10 @@ static void vc4_hdmi_set_spd_infoframe(struct drm_encoder *encoder)
static void vc4_hdmi_set_audio_infoframe(struct drm_encoder *encoder)
{
	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
	struct hdmi_audio_infoframe *audio = &vc4_hdmi->audio.infoframe;
	union hdmi_infoframe frame;

	hdmi_audio_infoframe_init(&frame.audio);

	frame.audio.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
	frame.audio.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
	frame.audio.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
	frame.audio.channels = vc4_hdmi->audio.channels;

	memcpy(&frame.audio, audio, sizeof(*audio));
	vc4_hdmi_write_infoframe(encoder, &frame);
}

@@ -1230,18 +1226,10 @@ static inline struct vc4_hdmi *dai_to_hdmi(struct snd_soc_dai *dai)
	return snd_soc_card_get_drvdata(card);
}

static int vc4_hdmi_audio_startup(struct snd_pcm_substream *substream,
				  struct snd_soc_dai *dai)
static int vc4_hdmi_audio_startup(struct device *dev, void *data)
{
	struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai);
	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
	struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base;
	struct drm_connector *connector = &vc4_hdmi->connector;
	int ret;

	if (vc4_hdmi->audio.substream && vc4_hdmi->audio.substream != substream)
		return -EINVAL;

	vc4_hdmi->audio.substream = substream;

	/*
	 * If the HDMI encoder hasn't probed, or the encoder is
@@ -1251,15 +1239,18 @@ static int vc4_hdmi_audio_startup(struct snd_pcm_substream *substream,
				VC4_HDMI_RAM_PACKET_ENABLE))
		return -ENODEV;

	ret = snd_pcm_hw_constraint_eld(substream->runtime, connector->eld);
	if (ret)
		return ret;
	vc4_hdmi->audio.streaming = true;

	return 0;
}
	HDMI_WRITE(HDMI_MAI_CTL,
		   VC4_HD_MAI_CTL_RESET |
		   VC4_HD_MAI_CTL_FLUSH |
		   VC4_HD_MAI_CTL_DLATE |
		   VC4_HD_MAI_CTL_ERRORE |
		   VC4_HD_MAI_CTL_ERRORF);

	if (vc4_hdmi->variant->phy_rng_enable)
		vc4_hdmi->variant->phy_rng_enable(vc4_hdmi);

static int vc4_hdmi_audio_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
	return 0;
}

@@ -1279,17 +1270,20 @@ static void vc4_hdmi_audio_reset(struct vc4_hdmi *vc4_hdmi)
	HDMI_WRITE(HDMI_MAI_CTL, VC4_HD_MAI_CTL_FLUSH);
}

static void vc4_hdmi_audio_shutdown(struct snd_pcm_substream *substream,
				    struct snd_soc_dai *dai)
static void vc4_hdmi_audio_shutdown(struct device *dev, void *data)
{
	struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai);
	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);

	if (substream != vc4_hdmi->audio.substream)
		return;
	HDMI_WRITE(HDMI_MAI_CTL,
		   VC4_HD_MAI_CTL_DLATE |
		   VC4_HD_MAI_CTL_ERRORE |
		   VC4_HD_MAI_CTL_ERRORF);

	vc4_hdmi_audio_reset(vc4_hdmi);
	if (vc4_hdmi->variant->phy_rng_disable)
		vc4_hdmi->variant->phy_rng_disable(vc4_hdmi);

	vc4_hdmi->audio.substream = NULL;
	vc4_hdmi->audio.streaming = false;
	vc4_hdmi_audio_reset(vc4_hdmi);
}

static int sample_rate_to_mai_fmt(int samplerate)
@@ -1331,38 +1325,37 @@ static int sample_rate_to_mai_fmt(int samplerate)
}

/* HDMI audio codec callbacks */
static int vc4_hdmi_audio_hw_params(struct snd_pcm_substream *substream,
				    struct snd_pcm_hw_params *params,
				    struct snd_soc_dai *dai)
static int vc4_hdmi_audio_prepare(struct device *dev, void *data,
				  struct hdmi_codec_daifmt *daifmt,
				  struct hdmi_codec_params *params)
{
	struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai);
	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
	struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base;
	struct device *dev = &vc4_hdmi->pdev->dev;
	u32 audio_packet_config, channel_mask;
	u32 channel_map;
	u32 mai_audio_format;
	u32 mai_sample_rate;

	if (substream != vc4_hdmi->audio.substream)
		return -EINVAL;

	dev_dbg(dev, "%s: %u Hz, %d bit, %d channels\n", __func__,
		params_rate(params), params_width(params),
		params_channels(params));
		params->sample_rate, params->sample_width,
		params->channels);

	vc4_hdmi->audio.channels = params_channels(params);
	vc4_hdmi->audio.samplerate = params_rate(params);
	vc4_hdmi->audio.channels = params->channels;
	vc4_hdmi->audio.samplerate = params->sample_rate;

	HDMI_WRITE(HDMI_MAI_CTL,
		   VC4_HD_MAI_CTL_RESET |
		   VC4_HD_MAI_CTL_FLUSH |
		   VC4_HD_MAI_CTL_DLATE |
		   VC4_HD_MAI_CTL_ERRORE |
		   VC4_HD_MAI_CTL_ERRORF);
		   VC4_SET_FIELD(params->channels, VC4_HD_MAI_CTL_CHNUM) |
		   VC4_HD_MAI_CTL_WHOLSMP |
		   VC4_HD_MAI_CTL_CHALIGN |
		   VC4_HD_MAI_CTL_ENABLE);

	vc4_hdmi_audio_set_mai_clock(vc4_hdmi);

	mai_sample_rate = sample_rate_to_mai_fmt(vc4_hdmi->audio.samplerate);
	if (params->iec.status[0] & IEC958_AES0_NONAUDIO &&
	    params->channels == 8)
		mai_audio_format = VC4_HDMI_MAI_FORMAT_HBR;
	else
		mai_audio_format = VC4_HDMI_MAI_FORMAT_PCM;
	HDMI_WRITE(HDMI_MAI_FMT,
		   VC4_SET_FIELD(mai_sample_rate,
@@ -1397,94 +1390,12 @@ static int vc4_hdmi_audio_hw_params(struct snd_pcm_substream *substream,
	HDMI_WRITE(HDMI_AUDIO_PACKET_CONFIG, audio_packet_config);
	vc4_hdmi_set_n_cts(vc4_hdmi);

	memcpy(&vc4_hdmi->audio.infoframe, &params->cea, sizeof(params->cea));
	vc4_hdmi_set_audio_infoframe(encoder);

	return 0;
}

static int vc4_hdmi_audio_trigger(struct snd_pcm_substream *substream, int cmd,
				  struct snd_soc_dai *dai)
{
	struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai);

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		vc4_hdmi->audio.streaming = true;

		if (vc4_hdmi->variant->phy_rng_enable)
			vc4_hdmi->variant->phy_rng_enable(vc4_hdmi);

		HDMI_WRITE(HDMI_MAI_CTL,
			   VC4_SET_FIELD(vc4_hdmi->audio.channels,
					 VC4_HD_MAI_CTL_CHNUM) |
					 VC4_HD_MAI_CTL_WHOLSMP |
					 VC4_HD_MAI_CTL_CHALIGN |
					 VC4_HD_MAI_CTL_ENABLE);
		break;
	case SNDRV_PCM_TRIGGER_STOP:
		HDMI_WRITE(HDMI_MAI_CTL,
			   VC4_HD_MAI_CTL_DLATE |
			   VC4_HD_MAI_CTL_ERRORE |
			   VC4_HD_MAI_CTL_ERRORF);

		if (vc4_hdmi->variant->phy_rng_disable)
			vc4_hdmi->variant->phy_rng_disable(vc4_hdmi);

		vc4_hdmi->audio.streaming = false;

		break;
	default:
		break;
	}

	return 0;
}

static inline struct vc4_hdmi *
snd_component_to_hdmi(struct snd_soc_component *component)
{
	struct snd_soc_card *card = snd_soc_component_get_drvdata(component);

	return snd_soc_card_get_drvdata(card);
}

static int vc4_hdmi_audio_eld_ctl_info(struct snd_kcontrol *kcontrol,
				       struct snd_ctl_elem_info *uinfo)
{
	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
	struct vc4_hdmi *vc4_hdmi = snd_component_to_hdmi(component);
	struct drm_connector *connector = &vc4_hdmi->connector;

	uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
	uinfo->count = sizeof(connector->eld);

	return 0;
}

static int vc4_hdmi_audio_eld_ctl_get(struct snd_kcontrol *kcontrol,
				      struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
	struct vc4_hdmi *vc4_hdmi = snd_component_to_hdmi(component);
	struct drm_connector *connector = &vc4_hdmi->connector;

	memcpy(ucontrol->value.bytes.data, connector->eld,
	       sizeof(connector->eld));

	return 0;
}

static const struct snd_kcontrol_new vc4_hdmi_audio_controls[] = {
	{
		.access = SNDRV_CTL_ELEM_ACCESS_READ |
			  SNDRV_CTL_ELEM_ACCESS_VOLATILE,
		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
		.name = "ELD",
		.info = vc4_hdmi_audio_eld_ctl_info,
		.get = vc4_hdmi_audio_eld_ctl_get,
	},
};

static const struct snd_soc_dapm_widget vc4_hdmi_audio_widgets[] = {
	SND_SOC_DAPM_OUTPUT("TX"),
};
@@ -1495,8 +1406,6 @@ static const struct snd_soc_dapm_route vc4_hdmi_audio_routes[] = {

static const struct snd_soc_component_driver vc4_hdmi_audio_component_drv = {
	.name			= "vc4-hdmi-codec-dai-component",
	.controls		= vc4_hdmi_audio_controls,
	.num_controls		= ARRAY_SIZE(vc4_hdmi_audio_controls),
	.dapm_widgets		= vc4_hdmi_audio_widgets,
	.num_dapm_widgets	= ARRAY_SIZE(vc4_hdmi_audio_widgets),
	.dapm_routes		= vc4_hdmi_audio_routes,
@@ -1507,28 +1416,6 @@ static const struct snd_soc_component_driver vc4_hdmi_audio_component_drv = {
	.non_legacy_dai_naming	= 1,
};

static const struct snd_soc_dai_ops vc4_hdmi_audio_dai_ops = {
	.startup = vc4_hdmi_audio_startup,
	.shutdown = vc4_hdmi_audio_shutdown,
	.hw_params = vc4_hdmi_audio_hw_params,
	.set_fmt = vc4_hdmi_audio_set_fmt,
	.trigger = vc4_hdmi_audio_trigger,
};

static struct snd_soc_dai_driver vc4_hdmi_audio_codec_dai_drv = {
	.name = "vc4-hdmi-hifi",
	.playback = {
		.stream_name = "Playback",
		.channels_min = 2,
		.channels_max = 8,
		.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
			 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |
			 SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |
			 SNDRV_PCM_RATE_192000,
		.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
	},
};

static const struct snd_soc_component_driver vc4_hdmi_audio_cpu_dai_comp = {
	.name = "vc4-hdmi-cpu-dai-component",
};
@@ -1555,7 +1442,6 @@ static struct snd_soc_dai_driver vc4_hdmi_audio_cpu_dai_drv = {
			 SNDRV_PCM_RATE_192000,
		.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
	},
	.ops = &vc4_hdmi_audio_dai_ops,
};

static const struct snd_dmaengine_pcm_config pcm_conf = {
@@ -1563,6 +1449,30 @@ static const struct snd_dmaengine_pcm_config pcm_conf = {
	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
};

static int vc4_hdmi_audio_get_eld(struct device *dev, void *data,
				  uint8_t *buf, size_t len)
{
	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
	struct drm_connector *connector = &vc4_hdmi->connector;

	memcpy(buf, connector->eld, min(sizeof(connector->eld), len));

	return 0;
}

static const struct hdmi_codec_ops vc4_hdmi_codec_ops = {
	.get_eld = vc4_hdmi_audio_get_eld,
	.prepare = vc4_hdmi_audio_prepare,
	.audio_shutdown = vc4_hdmi_audio_shutdown,
	.audio_startup = vc4_hdmi_audio_startup,
};

struct hdmi_codec_pdata vc4_hdmi_codec_pdata = {
	.ops = &vc4_hdmi_codec_ops,
	.max_i2s_channels = 8,
	.i2s = 1,
};

static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
{
	const struct vc4_hdmi_register *mai_data =
@@ -1570,6 +1480,7 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
	struct snd_soc_dai_link *dai_link = &vc4_hdmi->audio.link;
	struct snd_soc_card *card = &vc4_hdmi->audio.card;
	struct device *dev = &vc4_hdmi->pdev->dev;
	struct platform_device *codec_pdev;
	const __be32 *addr;
	int index;
	int ret;
@@ -1616,12 +1527,13 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
		return ret;
	}

	/* register component and codec dai */
	ret = devm_snd_soc_register_component(dev, &vc4_hdmi_audio_component_drv,
				     &vc4_hdmi_audio_codec_dai_drv, 1);
	if (ret) {
		dev_err(dev, "Could not register component: %d\n", ret);
		return ret;
	codec_pdev = platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
						   PLATFORM_DEVID_AUTO,
						   &vc4_hdmi_codec_pdata,
						   sizeof(vc4_hdmi_codec_pdata));
	if (IS_ERR(codec_pdev)) {
		dev_err(dev, "Couldn't register the HDMI codec: %ld\n", PTR_ERR(codec_pdev));
		return PTR_ERR(codec_pdev);
	}

	dai_link->cpus		= &vc4_hdmi->audio.cpu;
@@ -1634,9 +1546,9 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)

	dai_link->name = "MAI";
	dai_link->stream_name = "MAI PCM";
	dai_link->codecs->dai_name = vc4_hdmi_audio_codec_dai_drv.name;
	dai_link->codecs->dai_name = "i2s-hifi";
	dai_link->cpus->dai_name = dev_name(dev);
	dai_link->codecs->name = dev_name(dev);
	dai_link->codecs->name = dev_name(&codec_pdev->dev);
	dai_link->platforms->name = dev_name(dev);

	card->dai_link = dai_link;
+1 −2
Original line number Diff line number Diff line
@@ -114,8 +114,7 @@ struct vc4_hdmi_audio {
	int samplerate;
	int channels;
	struct snd_dmaengine_dai_dma_data dma_data;
	struct snd_pcm_substream *substream;

	struct hdmi_audio_infoframe infoframe;
	bool streaming;
};