|
@@ -48,11 +48,27 @@
|
|
|
#include "linux/clk.h"
|
|
|
#include "linux/component.h"
|
|
|
#include "linux/i2c.h"
|
|
|
+#include "linux/of_address.h"
|
|
|
#include "linux/of_gpio.h"
|
|
|
#include "linux/of_platform.h"
|
|
|
+#include "linux/rational.h"
|
|
|
+#include "sound/dmaengine_pcm.h"
|
|
|
+#include "sound/pcm_drm_eld.h"
|
|
|
+#include "sound/pcm_params.h"
|
|
|
+#include "sound/soc.h"
|
|
|
#include "vc4_drv.h"
|
|
|
#include "vc4_regs.h"
|
|
|
|
|
|
+/* HDMI audio information */
|
|
|
+struct vc4_hdmi_audio {
|
|
|
+ struct snd_soc_card card;
|
|
|
+ struct snd_soc_dai_link link;
|
|
|
+ int samplerate;
|
|
|
+ int channels;
|
|
|
+ struct snd_dmaengine_dai_dma_data dma_data;
|
|
|
+ struct snd_pcm_substream *substream;
|
|
|
+};
|
|
|
+
|
|
|
/* General HDMI hardware state. */
|
|
|
struct vc4_hdmi {
|
|
|
struct platform_device *pdev;
|
|
@@ -60,6 +76,8 @@ struct vc4_hdmi {
|
|
|
struct drm_encoder *encoder;
|
|
|
struct drm_connector *connector;
|
|
|
|
|
|
+ struct vc4_hdmi_audio audio;
|
|
|
+
|
|
|
struct i2c_adapter *ddc;
|
|
|
void __iomem *hdmicore_regs;
|
|
|
void __iomem *hd_regs;
|
|
@@ -115,6 +133,10 @@ static const struct {
|
|
|
HDMI_REG(VC4_HDMI_SW_RESET_CONTROL),
|
|
|
HDMI_REG(VC4_HDMI_HOTPLUG_INT),
|
|
|
HDMI_REG(VC4_HDMI_HOTPLUG),
|
|
|
+ HDMI_REG(VC4_HDMI_MAI_CHANNEL_MAP),
|
|
|
+ HDMI_REG(VC4_HDMI_MAI_CONFIG),
|
|
|
+ HDMI_REG(VC4_HDMI_MAI_FORMAT),
|
|
|
+ HDMI_REG(VC4_HDMI_AUDIO_PACKET_CONFIG),
|
|
|
HDMI_REG(VC4_HDMI_RAM_PACKET_CONFIG),
|
|
|
HDMI_REG(VC4_HDMI_HORZA),
|
|
|
HDMI_REG(VC4_HDMI_HORZB),
|
|
@@ -125,6 +147,7 @@ static const struct {
|
|
|
HDMI_REG(VC4_HDMI_VERTB0),
|
|
|
HDMI_REG(VC4_HDMI_VERTB1),
|
|
|
HDMI_REG(VC4_HDMI_TX_PHY_RESET_CTL),
|
|
|
+ HDMI_REG(VC4_HDMI_TX_PHY_CTL0),
|
|
|
};
|
|
|
|
|
|
static const struct {
|
|
@@ -133,6 +156,9 @@ static const struct {
|
|
|
} hd_regs[] = {
|
|
|
HDMI_REG(VC4_HD_M_CTL),
|
|
|
HDMI_REG(VC4_HD_MAI_CTL),
|
|
|
+ HDMI_REG(VC4_HD_MAI_THR),
|
|
|
+ HDMI_REG(VC4_HD_MAI_FMT),
|
|
|
+ HDMI_REG(VC4_HD_MAI_SMP),
|
|
|
HDMI_REG(VC4_HD_VID_CTL),
|
|
|
HDMI_REG(VC4_HD_CSC_CTL),
|
|
|
HDMI_REG(VC4_HD_FRAME_COUNT),
|
|
@@ -232,6 +258,7 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector)
|
|
|
|
|
|
drm_mode_connector_update_edid_property(connector, edid);
|
|
|
ret = drm_add_edid_modes(connector, edid);
|
|
|
+ drm_edid_to_eld(connector, edid);
|
|
|
|
|
|
return ret;
|
|
|
}
|
|
@@ -317,7 +344,7 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder,
|
|
|
struct drm_device *dev = encoder->dev;
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(dev);
|
|
|
u32 packet_id = frame->any.type - 0x80;
|
|
|
- u32 packet_reg = VC4_HDMI_GCP_0 + VC4_HDMI_PACKET_STRIDE * packet_id;
|
|
|
+ u32 packet_reg = VC4_HDMI_RAM_PACKET(packet_id);
|
|
|
uint8_t buffer[VC4_HDMI_PACKET_STRIDE];
|
|
|
ssize_t len, i;
|
|
|
int ret;
|
|
@@ -398,6 +425,24 @@ static void vc4_hdmi_set_spd_infoframe(struct drm_encoder *encoder)
|
|
|
vc4_hdmi_write_infoframe(encoder, &frame);
|
|
|
}
|
|
|
|
|
|
+static void vc4_hdmi_set_audio_infoframe(struct drm_encoder *encoder)
|
|
|
+{
|
|
|
+ struct drm_device *drm = encoder->dev;
|
|
|
+ struct vc4_dev *vc4 = drm->dev_private;
|
|
|
+ struct vc4_hdmi *hdmi = vc4->hdmi;
|
|
|
+ union hdmi_infoframe frame;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = 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 = hdmi->audio.channels;
|
|
|
+
|
|
|
+ vc4_hdmi_write_infoframe(encoder, &frame);
|
|
|
+}
|
|
|
+
|
|
|
static void vc4_hdmi_set_infoframes(struct drm_encoder *encoder)
|
|
|
{
|
|
|
vc4_hdmi_set_avi_infoframe(encoder);
|
|
@@ -606,6 +651,447 @@ static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = {
|
|
|
.enable = vc4_hdmi_encoder_enable,
|
|
|
};
|
|
|
|
|
|
+/* HDMI audio codec callbacks */
|
|
|
+static void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *hdmi)
|
|
|
+{
|
|
|
+ struct drm_device *drm = hdmi->encoder->dev;
|
|
|
+ struct vc4_dev *vc4 = to_vc4_dev(drm);
|
|
|
+ u32 hsm_clock = clk_get_rate(hdmi->hsm_clock);
|
|
|
+ unsigned long n, m;
|
|
|
+
|
|
|
+ rational_best_approximation(hsm_clock, hdmi->audio.samplerate,
|
|
|
+ VC4_HD_MAI_SMP_N_MASK >>
|
|
|
+ VC4_HD_MAI_SMP_N_SHIFT,
|
|
|
+ (VC4_HD_MAI_SMP_M_MASK >>
|
|
|
+ VC4_HD_MAI_SMP_M_SHIFT) + 1,
|
|
|
+ &n, &m);
|
|
|
+
|
|
|
+ HD_WRITE(VC4_HD_MAI_SMP,
|
|
|
+ VC4_SET_FIELD(n, VC4_HD_MAI_SMP_N) |
|
|
|
+ VC4_SET_FIELD(m - 1, VC4_HD_MAI_SMP_M));
|
|
|
+}
|
|
|
+
|
|
|
+static void vc4_hdmi_set_n_cts(struct vc4_hdmi *hdmi)
|
|
|
+{
|
|
|
+ struct drm_encoder *encoder = hdmi->encoder;
|
|
|
+ struct drm_crtc *crtc = encoder->crtc;
|
|
|
+ struct drm_device *drm = encoder->dev;
|
|
|
+ struct vc4_dev *vc4 = to_vc4_dev(drm);
|
|
|
+ const struct drm_display_mode *mode = &crtc->state->adjusted_mode;
|
|
|
+ u32 samplerate = hdmi->audio.samplerate;
|
|
|
+ u32 n, cts;
|
|
|
+ u64 tmp;
|
|
|
+
|
|
|
+ n = 128 * samplerate / 1000;
|
|
|
+ tmp = (u64)(mode->clock * 1000) * n;
|
|
|
+ do_div(tmp, 128 * samplerate);
|
|
|
+ cts = tmp;
|
|
|
+
|
|
|
+ HDMI_WRITE(VC4_HDMI_CRP_CFG,
|
|
|
+ VC4_HDMI_CRP_CFG_EXTERNAL_CTS_EN |
|
|
|
+ VC4_SET_FIELD(n, VC4_HDMI_CRP_CFG_N));
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We could get slightly more accurate clocks in some cases by
|
|
|
+ * providing a CTS_1 value. The two CTS values are alternated
|
|
|
+ * between based on the period fields
|
|
|
+ */
|
|
|
+ HDMI_WRITE(VC4_HDMI_CTS_0, cts);
|
|
|
+ HDMI_WRITE(VC4_HDMI_CTS_1, cts);
|
|
|
+}
|
|
|
+
|
|
|
+static inline struct vc4_hdmi *dai_to_hdmi(struct snd_soc_dai *dai)
|
|
|
+{
|
|
|
+ struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai);
|
|
|
+
|
|
|
+ return snd_soc_card_get_drvdata(card);
|
|
|
+}
|
|
|
+
|
|
|
+static int vc4_hdmi_audio_startup(struct snd_pcm_substream *substream,
|
|
|
+ struct snd_soc_dai *dai)
|
|
|
+{
|
|
|
+ struct vc4_hdmi *hdmi = dai_to_hdmi(dai);
|
|
|
+ struct drm_encoder *encoder = hdmi->encoder;
|
|
|
+ struct vc4_dev *vc4 = to_vc4_dev(encoder->dev);
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (hdmi->audio.substream && hdmi->audio.substream != substream)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ hdmi->audio.substream = substream;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If the HDMI encoder hasn't probed, or the encoder is
|
|
|
+ * currently in DVI mode, treat the codec dai as missing.
|
|
|
+ */
|
|
|
+ if (!encoder->crtc || !(HDMI_READ(VC4_HDMI_RAM_PACKET_CONFIG) &
|
|
|
+ VC4_HDMI_RAM_PACKET_ENABLE))
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ ret = snd_pcm_hw_constraint_eld(substream->runtime,
|
|
|
+ hdmi->connector->eld);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int vc4_hdmi_audio_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
|
+{
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void vc4_hdmi_audio_reset(struct vc4_hdmi *hdmi)
|
|
|
+{
|
|
|
+ struct drm_encoder *encoder = hdmi->encoder;
|
|
|
+ struct drm_device *drm = encoder->dev;
|
|
|
+ struct device *dev = &hdmi->pdev->dev;
|
|
|
+ struct vc4_dev *vc4 = to_vc4_dev(drm);
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = vc4_hdmi_stop_packet(encoder, HDMI_INFOFRAME_TYPE_AUDIO);
|
|
|
+ if (ret)
|
|
|
+ dev_err(dev, "Failed to stop audio infoframe: %d\n", ret);
|
|
|
+
|
|
|
+ HD_WRITE(VC4_HD_MAI_CTL, VC4_HD_MAI_CTL_RESET);
|
|
|
+ HD_WRITE(VC4_HD_MAI_CTL, VC4_HD_MAI_CTL_ERRORF);
|
|
|
+ HD_WRITE(VC4_HD_MAI_CTL, VC4_HD_MAI_CTL_FLUSH);
|
|
|
+}
|
|
|
+
|
|
|
+static void vc4_hdmi_audio_shutdown(struct snd_pcm_substream *substream,
|
|
|
+ struct snd_soc_dai *dai)
|
|
|
+{
|
|
|
+ struct vc4_hdmi *hdmi = dai_to_hdmi(dai);
|
|
|
+
|
|
|
+ if (substream != hdmi->audio.substream)
|
|
|
+ return;
|
|
|
+
|
|
|
+ vc4_hdmi_audio_reset(hdmi);
|
|
|
+
|
|
|
+ hdmi->audio.substream = NULL;
|
|
|
+}
|
|
|
+
|
|
|
+/* 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)
|
|
|
+{
|
|
|
+ struct vc4_hdmi *hdmi = dai_to_hdmi(dai);
|
|
|
+ struct drm_encoder *encoder = hdmi->encoder;
|
|
|
+ struct drm_device *drm = encoder->dev;
|
|
|
+ struct device *dev = &hdmi->pdev->dev;
|
|
|
+ struct vc4_dev *vc4 = to_vc4_dev(drm);
|
|
|
+ u32 audio_packet_config, channel_mask;
|
|
|
+ u32 channel_map, i;
|
|
|
+
|
|
|
+ if (substream != 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));
|
|
|
+
|
|
|
+ hdmi->audio.channels = params_channels(params);
|
|
|
+ hdmi->audio.samplerate = params_rate(params);
|
|
|
+
|
|
|
+ HD_WRITE(VC4_HD_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_hdmi_audio_set_mai_clock(hdmi);
|
|
|
+
|
|
|
+ audio_packet_config =
|
|
|
+ VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_SAMPLE_FLAT |
|
|
|
+ VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_INACTIVE_CHANNELS |
|
|
|
+ VC4_SET_FIELD(0xf, VC4_HDMI_AUDIO_PACKET_B_FRAME_IDENTIFIER);
|
|
|
+
|
|
|
+ channel_mask = GENMASK(hdmi->audio.channels - 1, 0);
|
|
|
+ audio_packet_config |= VC4_SET_FIELD(channel_mask,
|
|
|
+ VC4_HDMI_AUDIO_PACKET_CEA_MASK);
|
|
|
+
|
|
|
+ /* Set the MAI threshold. This logic mimics the firmware's. */
|
|
|
+ if (hdmi->audio.samplerate > 96000) {
|
|
|
+ HD_WRITE(VC4_HD_MAI_THR,
|
|
|
+ VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQHIGH) |
|
|
|
+ VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQLOW));
|
|
|
+ } else if (hdmi->audio.samplerate > 48000) {
|
|
|
+ HD_WRITE(VC4_HD_MAI_THR,
|
|
|
+ VC4_SET_FIELD(0x14, VC4_HD_MAI_THR_DREQHIGH) |
|
|
|
+ VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQLOW));
|
|
|
+ } else {
|
|
|
+ HD_WRITE(VC4_HD_MAI_THR,
|
|
|
+ VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICHIGH) |
|
|
|
+ VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICLOW) |
|
|
|
+ VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_DREQHIGH) |
|
|
|
+ VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_DREQLOW));
|
|
|
+ }
|
|
|
+
|
|
|
+ HDMI_WRITE(VC4_HDMI_MAI_CONFIG,
|
|
|
+ VC4_HDMI_MAI_CONFIG_BIT_REVERSE |
|
|
|
+ VC4_SET_FIELD(channel_mask, VC4_HDMI_MAI_CHANNEL_MASK));
|
|
|
+
|
|
|
+ channel_map = 0;
|
|
|
+ for (i = 0; i < 8; i++) {
|
|
|
+ if (channel_mask & BIT(i))
|
|
|
+ channel_map |= i << (3 * i);
|
|
|
+ }
|
|
|
+
|
|
|
+ HDMI_WRITE(VC4_HDMI_MAI_CHANNEL_MAP, channel_map);
|
|
|
+ HDMI_WRITE(VC4_HDMI_AUDIO_PACKET_CONFIG, audio_packet_config);
|
|
|
+ vc4_hdmi_set_n_cts(hdmi);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int vc4_hdmi_audio_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
|
+ struct snd_soc_dai *dai)
|
|
|
+{
|
|
|
+ struct vc4_hdmi *hdmi = dai_to_hdmi(dai);
|
|
|
+ struct drm_encoder *encoder = hdmi->encoder;
|
|
|
+ struct drm_device *drm = encoder->dev;
|
|
|
+ struct vc4_dev *vc4 = to_vc4_dev(drm);
|
|
|
+
|
|
|
+ switch (cmd) {
|
|
|
+ case SNDRV_PCM_TRIGGER_START:
|
|
|
+ vc4_hdmi_set_audio_infoframe(encoder);
|
|
|
+ HDMI_WRITE(VC4_HDMI_TX_PHY_CTL0,
|
|
|
+ HDMI_READ(VC4_HDMI_TX_PHY_CTL0) &
|
|
|
+ ~VC4_HDMI_TX_PHY_RNG_PWRDN);
|
|
|
+ HD_WRITE(VC4_HD_MAI_CTL,
|
|
|
+ VC4_SET_FIELD(hdmi->audio.channels,
|
|
|
+ VC4_HD_MAI_CTL_CHNUM) |
|
|
|
+ VC4_HD_MAI_CTL_ENABLE);
|
|
|
+ break;
|
|
|
+ case SNDRV_PCM_TRIGGER_STOP:
|
|
|
+ HD_WRITE(VC4_HD_MAI_CTL,
|
|
|
+ VC4_HD_MAI_CTL_DLATE |
|
|
|
+ VC4_HD_MAI_CTL_ERRORE |
|
|
|
+ VC4_HD_MAI_CTL_ERRORF);
|
|
|
+ HDMI_WRITE(VC4_HDMI_TX_PHY_CTL0,
|
|
|
+ HDMI_READ(VC4_HDMI_TX_PHY_CTL0) |
|
|
|
+ VC4_HDMI_TX_PHY_RNG_PWRDN);
|
|
|
+ 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 *hdmi = snd_component_to_hdmi(component);
|
|
|
+
|
|
|
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
|
|
|
+ uinfo->count = sizeof(hdmi->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 *hdmi = snd_component_to_hdmi(component);
|
|
|
+
|
|
|
+ memcpy(ucontrol->value.bytes.data, hdmi->connector->eld,
|
|
|
+ sizeof(hdmi->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"),
|
|
|
+};
|
|
|
+
|
|
|
+static const struct snd_soc_dapm_route vc4_hdmi_audio_routes[] = {
|
|
|
+ { "TX", NULL, "Playback" },
|
|
|
+};
|
|
|
+
|
|
|
+static const struct snd_soc_codec_driver vc4_hdmi_audio_codec_drv = {
|
|
|
+ .component_driver = {
|
|
|
+ .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,
|
|
|
+ .num_dapm_routes = ARRAY_SIZE(vc4_hdmi_audio_routes),
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+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",
|
|
|
+};
|
|
|
+
|
|
|
+static int vc4_hdmi_audio_cpu_dai_probe(struct snd_soc_dai *dai)
|
|
|
+{
|
|
|
+ struct vc4_hdmi *hdmi = dai_to_hdmi(dai);
|
|
|
+
|
|
|
+ snd_soc_dai_init_dma_data(dai, &hdmi->audio.dma_data, NULL);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static struct snd_soc_dai_driver vc4_hdmi_audio_cpu_dai_drv = {
|
|
|
+ .name = "vc4-hdmi-cpu-dai",
|
|
|
+ .probe = vc4_hdmi_audio_cpu_dai_probe,
|
|
|
+ .playback = {
|
|
|
+ .stream_name = "Playback",
|
|
|
+ .channels_min = 1,
|
|
|
+ .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,
|
|
|
+ },
|
|
|
+ .ops = &vc4_hdmi_audio_dai_ops,
|
|
|
+};
|
|
|
+
|
|
|
+static const struct snd_dmaengine_pcm_config pcm_conf = {
|
|
|
+ .chan_names[SNDRV_PCM_STREAM_PLAYBACK] = "audio-rx",
|
|
|
+ .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
|
|
|
+};
|
|
|
+
|
|
|
+static int vc4_hdmi_audio_init(struct vc4_hdmi *hdmi)
|
|
|
+{
|
|
|
+ struct snd_soc_dai_link *dai_link = &hdmi->audio.link;
|
|
|
+ struct snd_soc_card *card = &hdmi->audio.card;
|
|
|
+ struct device *dev = &hdmi->pdev->dev;
|
|
|
+ const __be32 *addr;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (!of_find_property(dev->of_node, "dmas", NULL)) {
|
|
|
+ dev_warn(dev,
|
|
|
+ "'dmas' DT property is missing, no HDMI audio\n");
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Get the physical address of VC4_HD_MAI_DATA. We need to retrieve
|
|
|
+ * the bus address specified in the DT, because the physical address
|
|
|
+ * (the one returned by platform_get_resource()) is not appropriate
|
|
|
+ * for DMA transfers.
|
|
|
+ * This VC/MMU should probably be exposed to avoid this kind of hacks.
|
|
|
+ */
|
|
|
+ addr = of_get_address(dev->of_node, 1, NULL, NULL);
|
|
|
+ hdmi->audio.dma_data.addr = be32_to_cpup(addr) + VC4_HD_MAI_DATA;
|
|
|
+ hdmi->audio.dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
|
+ hdmi->audio.dma_data.maxburst = 2;
|
|
|
+
|
|
|
+ ret = devm_snd_dmaengine_pcm_register(dev, &pcm_conf, 0);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(dev, "Could not register PCM component: %d\n", ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = devm_snd_soc_register_component(dev, &vc4_hdmi_audio_cpu_dai_comp,
|
|
|
+ &vc4_hdmi_audio_cpu_dai_drv, 1);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(dev, "Could not register CPU DAI: %d\n", ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* register codec and codec dai */
|
|
|
+ ret = snd_soc_register_codec(dev, &vc4_hdmi_audio_codec_drv,
|
|
|
+ &vc4_hdmi_audio_codec_dai_drv, 1);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(dev, "Could not register codec: %d\n", ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ dai_link->name = "MAI";
|
|
|
+ dai_link->stream_name = "MAI PCM";
|
|
|
+ dai_link->codec_dai_name = vc4_hdmi_audio_codec_dai_drv.name;
|
|
|
+ dai_link->cpu_dai_name = dev_name(dev);
|
|
|
+ dai_link->codec_name = dev_name(dev);
|
|
|
+ dai_link->platform_name = dev_name(dev);
|
|
|
+
|
|
|
+ card->dai_link = dai_link;
|
|
|
+ card->num_links = 1;
|
|
|
+ card->name = "vc4-hdmi";
|
|
|
+ card->dev = dev;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Be careful, snd_soc_register_card() calls dev_set_drvdata() and
|
|
|
+ * stores a pointer to the snd card object in dev->driver_data. This
|
|
|
+ * means we cannot use it for something else. The hdmi back-pointer is
|
|
|
+ * now stored in card->drvdata and should be retrieved with
|
|
|
+ * snd_soc_card_get_drvdata() if needed.
|
|
|
+ */
|
|
|
+ snd_soc_card_set_drvdata(card, hdmi);
|
|
|
+ ret = devm_snd_soc_register_card(dev, card);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(dev, "Could not register sound card: %d\n", ret);
|
|
|
+ goto unregister_codec;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+unregister_codec:
|
|
|
+ snd_soc_unregister_codec(dev);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void vc4_hdmi_audio_cleanup(struct vc4_hdmi *hdmi)
|
|
|
+{
|
|
|
+ struct device *dev = &hdmi->pdev->dev;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If drvdata is not set this means the audio card was not
|
|
|
+ * registered, just skip codec unregistration in this case.
|
|
|
+ */
|
|
|
+ if (dev_get_drvdata(dev))
|
|
|
+ snd_soc_unregister_codec(dev);
|
|
|
+}
|
|
|
+
|
|
|
static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
|
|
|
{
|
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
@@ -737,6 +1223,10 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
|
|
|
goto err_destroy_encoder;
|
|
|
}
|
|
|
|
|
|
+ ret = vc4_hdmi_audio_init(hdmi);
|
|
|
+ if (ret)
|
|
|
+ goto err_destroy_encoder;
|
|
|
+
|
|
|
return 0;
|
|
|
|
|
|
err_destroy_encoder:
|
|
@@ -758,6 +1248,8 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master,
|
|
|
struct vc4_dev *vc4 = drm->dev_private;
|
|
|
struct vc4_hdmi *hdmi = vc4->hdmi;
|
|
|
|
|
|
+ vc4_hdmi_audio_cleanup(hdmi);
|
|
|
+
|
|
|
vc4_hdmi_connector_destroy(hdmi->connector);
|
|
|
vc4_hdmi_encoder_destroy(hdmi->encoder);
|
|
|
|