Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions arch/arm/boot/dts/bcm2711-rpi.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
clock-names = "hdmi";
resets = <&dvp 0>;
ddc = <&ddc0>;
dmas = <&dma 10>;
dmas = <&dma (10|(1<<27))>;
dma-names = "audio-rx";
interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
status = "disabled";
Expand Down Expand Up @@ -114,7 +114,7 @@
clocks = <&firmware_clocks 13>;
clock-names = "hdmi";
resets = <&dvp 1>;
dmas = <&dma 17>;
dmas = <&dma (17|(1<<27))>;
dma-names = "audio-rx";
interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
status = "disabled";
Expand Down
2 changes: 1 addition & 1 deletion arch/arm/boot/dts/bcm2835-common.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
clocks = <&clocks BCM2835_PLLH_PIX>,
<&clocks BCM2835_CLOCK_HSM>;
clock-names = "pixel", "hdmi";
dmas = <&dma 17>;
dmas = <&dma (17|(1<<27))>;
dma-names = "audio-rx";
status = "disabled";
};
Expand Down
152 changes: 128 additions & 24 deletions drivers/gpu/drm/vc4/vc4_hdmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include <linux/pm_runtime.h>
#include <linux/rational.h>
#include <linux/reset.h>
#include <sound/asoundef.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_drm_eld.h>
#include <sound/pcm_params.h>
Expand Down Expand Up @@ -917,25 +918,68 @@ static void vc4_hdmi_audio_shutdown(struct snd_pcm_substream *substream,
vc4_hdmi->audio.substream = NULL;
}

static int sample_rate_to_mai_fmt(int samplerate)
{
switch(samplerate)
{
case 8000:
return VC4_HDMI_MAI_SAMPLE_RATE_8000;
case 11025:
return VC4_HDMI_MAI_SAMPLE_RATE_11025;
case 12000:
return VC4_HDMI_MAI_SAMPLE_RATE_12000;
case 16000:
return VC4_HDMI_MAI_SAMPLE_RATE_16000;
case 22050:
return VC4_HDMI_MAI_SAMPLE_RATE_22050;
case 24000:
return VC4_HDMI_MAI_SAMPLE_RATE_24000;
case 32000:
return VC4_HDMI_MAI_SAMPLE_RATE_32000;
case 44100:
return VC4_HDMI_MAI_SAMPLE_RATE_44100;
case 48000:
return VC4_HDMI_MAI_SAMPLE_RATE_48000;
case 64000:
return VC4_HDMI_MAI_SAMPLE_RATE_64000;
case 88200:
return VC4_HDMI_MAI_SAMPLE_RATE_88200;
case 96000:
return VC4_HDMI_MAI_SAMPLE_RATE_96000;
case 128000:
return VC4_HDMI_MAI_SAMPLE_RATE_128000;
case 176400:
return VC4_HDMI_MAI_SAMPLE_RATE_176400;
case 192000:
return VC4_HDMI_MAI_SAMPLE_RATE_192000;
default:
return VC4_HDMI_MAI_SAMPLE_RATE_NOT_INDICATED;
}
}

/* HDMI audio codec callbacks */
static int vc4_hdmi_audio_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
static int vc4_hdmi_audio_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai);
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));
dev_dbg(dev, "%s: %u Hz, %d bit, %d channels AES0=%02x\n",
__func__,
substream->runtime->rate,
snd_pcm_format_width(substream->runtime->format),
substream->runtime->channels,
vc4_hdmi->audio.iec_status[0]);

vc4_hdmi->audio.channels = params_channels(params);
vc4_hdmi->audio.samplerate = params_rate(params);
vc4_hdmi->audio.channels = substream->runtime->channels;
vc4_hdmi->audio.samplerate = substream->runtime->rate;

HDMI_WRITE(HDMI_MAI_CTL,
VC4_HD_MAI_CTL_RESET |
Expand All @@ -946,6 +990,16 @@ static int vc4_hdmi_audio_hw_params(struct snd_pcm_substream *substream,

vc4_hdmi_audio_set_mai_clock(vc4_hdmi);

mai_sample_rate = sample_rate_to_mai_fmt(vc4_hdmi->audio.samplerate);
if (vc4_hdmi->audio.iec_status[0] & IEC958_AES0_NONAUDIO &&
vc4_hdmi->audio.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, VC4_HDMI_MAI_FORMAT_SAMPLE_RATE) |
VC4_SET_FIELD(mai_audio_format, VC4_HDMI_MAI_FORMAT_AUDIO_FORMAT));

/* The B frame identifier should match the value used by alsa-lib (8) */
audio_packet_config =
VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_SAMPLE_FLAT |
Expand All @@ -956,25 +1010,16 @@ static int vc4_hdmi_audio_hw_params(struct snd_pcm_substream *substream,
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 (vc4_hdmi->audio.samplerate > 96000) {
HDMI_WRITE(HDMI_MAI_THR,
VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQHIGH) |
VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQLOW));
} else if (vc4_hdmi->audio.samplerate > 48000) {
HDMI_WRITE(HDMI_MAI_THR,
VC4_SET_FIELD(0x14, VC4_HD_MAI_THR_DREQHIGH) |
VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQLOW));
} else {
HDMI_WRITE(HDMI_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));
}
/* Set the MAI threshold */
HDMI_WRITE(HDMI_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(HDMI_MAI_CONFIG,
VC4_HDMI_MAI_CONFIG_BIT_REVERSE |
VC4_HDMI_MAI_CONFIG_FORMAT_REVERSE |
VC4_SET_FIELD(channel_mask, VC4_HDMI_MAI_CHANNEL_MASK));

channel_map = vc4_hdmi->variant->channel_map(vc4_hdmi, channel_mask);
Expand Down Expand Up @@ -1059,6 +1104,47 @@ static int vc4_hdmi_audio_eld_ctl_get(struct snd_kcontrol *kcontrol,
return 0;
}

static int vc4_spdif_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}

static int vc4_spdif_playback_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);

memcpy(ucontrol->value.iec958.status, vc4_hdmi->audio.iec_status,
sizeof(vc4_hdmi->audio.iec_status));

return 0;
}

static int vc4_spdif_playback_put(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);

memcpy(vc4_hdmi->audio.iec_status, ucontrol->value.iec958.status,
sizeof(vc4_hdmi->audio.iec_status));

return 0;
}

static int vc4_spdif_mask_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
memset(ucontrol->value.iec958.status, 0xff,
FIELD_SIZEOF(struct vc4_hdmi_audio, iec_status));

return 0;
}

static const struct snd_kcontrol_new vc4_hdmi_audio_controls[] = {
{
.access = SNDRV_CTL_ELEM_ACCESS_READ |
Expand All @@ -1068,6 +1154,19 @@ static const struct snd_kcontrol_new vc4_hdmi_audio_controls[] = {
.info = vc4_hdmi_audio_eld_ctl_info,
.get = vc4_hdmi_audio_eld_ctl_get,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
.info = vc4_spdif_info,
.get = vc4_spdif_playback_get,
.put = vc4_spdif_playback_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
.info = vc4_spdif_info,
.get = vc4_spdif_mask_get,
},
};

static const struct snd_soc_dapm_widget vc4_hdmi_audio_widgets[] = {
Expand All @@ -1094,7 +1193,7 @@ static const struct snd_soc_component_driver vc4_hdmi_audio_component_drv = {
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,
.prepare = vc4_hdmi_audio_prepare,
.set_fmt = vc4_hdmi_audio_set_fmt,
.trigger = vc4_hdmi_audio_trigger,
};
Expand Down Expand Up @@ -1188,6 +1287,11 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
vc4_hdmi->audio.dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
vc4_hdmi->audio.dma_data.maxburst = 2;

vc4_hdmi->audio.iec_status[0] = IEC958_AES0_CON_NOT_COPYRIGHT;
vc4_hdmi->audio.iec_status[1] =
IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER;
vc4_hdmi->audio.iec_status[3] = IEC958_AES3_CON_FS_48000;

ret = devm_snd_dmaengine_pcm_register(dev, &pcm_conf, 0);
if (ret) {
dev_err(dev, "Could not register PCM component: %d\n", ret);
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpu/drm/vc4/vc4_hdmi.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ struct vc4_hdmi_audio {
struct snd_pcm_substream *substream;

bool streaming;

unsigned char iec_status[4];
};

/* General HDMI hardware state. */
Expand Down
31 changes: 31 additions & 0 deletions drivers/gpu/drm/vc4/vc4_regs.h
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,37 @@
# define VC4_HDMI_AUDIO_PACKET_CEA_MASK_MASK VC4_MASK(7, 0)
# define VC4_HDMI_AUDIO_PACKET_CEA_MASK_SHIFT 0


# define VC4_HDMI_MAI_FORMAT_AUDIO_FORMAT_MASK VC4_MASK(23, 16)
# define VC4_HDMI_MAI_FORMAT_AUDIO_FORMAT_SHIFT 16

enum {
VC4_HDMI_MAI_FORMAT_PCM = 2,
VC4_HDMI_MAI_FORMAT_HBR = 200,
};

# define VC4_HDMI_MAI_FORMAT_SAMPLE_RATE_MASK VC4_MASK(15, 8)
# define VC4_HDMI_MAI_FORMAT_SAMPLE_RATE_SHIFT 8

enum {
VC4_HDMI_MAI_SAMPLE_RATE_NOT_INDICATED = 0,
VC4_HDMI_MAI_SAMPLE_RATE_8000 = 1,
VC4_HDMI_MAI_SAMPLE_RATE_11025 = 2,
VC4_HDMI_MAI_SAMPLE_RATE_12000 = 3,
VC4_HDMI_MAI_SAMPLE_RATE_16000 = 4,
VC4_HDMI_MAI_SAMPLE_RATE_22050 = 5,
VC4_HDMI_MAI_SAMPLE_RATE_24000 = 6,
VC4_HDMI_MAI_SAMPLE_RATE_32000 = 7,
VC4_HDMI_MAI_SAMPLE_RATE_44100 = 8,
VC4_HDMI_MAI_SAMPLE_RATE_48000 = 9,
VC4_HDMI_MAI_SAMPLE_RATE_64000 = 10,
VC4_HDMI_MAI_SAMPLE_RATE_88200 = 11,
VC4_HDMI_MAI_SAMPLE_RATE_96000 = 12,
VC4_HDMI_MAI_SAMPLE_RATE_128000 = 13,
VC4_HDMI_MAI_SAMPLE_RATE_176400 = 14,
VC4_HDMI_MAI_SAMPLE_RATE_192000 = 15,
};

# define VC4_HDMI_RAM_PACKET_ENABLE BIT(16)

/* When set, the CTS_PERIOD counts based on MAI bus sync pulse instead
Expand Down