diff mbox series

drm/tegra: sor: Support for audio over HDMI

Message ID 20181203153642.13562-1-thierry.reding@gmail.com (mailing list archive)
State Accepted
Headers show
Series drm/tegra: sor: Support for audio over HDMI | expand

Commit Message

Thierry Reding Dec. 3, 2018, 3:36 p.m. UTC
From: Thierry Reding <treding@nvidia.com>

This code is very similar to the audio over HDMI support on older chips.
Interoperation with the audio codec is done via a pair of codec scratch
registers and an interrupt that is raised at the SOR when the codec has
written those registers.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
 drivers/gpu/drm/tegra/sor.c | 229 ++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tegra/sor.h |  68 +++++++++++
 2 files changed, 297 insertions(+)

Comments

Jani Nikula Dec. 3, 2018, 4:05 p.m. UTC | #1
On Mon, 03 Dec 2018, Thierry Reding <thierry.reding@gmail.com> wrote:
> From: Thierry Reding <treding@nvidia.com>
>
> This code is very similar to the audio over HDMI support on older chips.
> Interoperation with the audio codec is done via a pair of codec scratch
> registers and an interrupt that is raised at the SOR when the codec has
> written those registers.
>
> Signed-off-by: Thierry Reding <treding@nvidia.com>
> ---
>  drivers/gpu/drm/tegra/sor.c | 229 ++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/tegra/sor.h |  68 +++++++++++
>  2 files changed, 297 insertions(+)
>
> diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c
> index b129da2e5afd..22a54434a757 100644
> --- a/drivers/gpu/drm/tegra/sor.c
> +++ b/drivers/gpu/drm/tegra/sor.c
> @@ -19,6 +19,8 @@
>  
>  #include <soc/tegra/pmc.h>
>  
> +#include <sound/hda_verbs.h>
> +
>  #include <drm/drm_atomic_helper.h>
>  #include <drm/drm_dp_helper.h>
>  #include <drm/drm_panel.h>
> @@ -407,6 +409,7 @@ struct tegra_sor {
>  	const struct tegra_sor_soc *soc;
>  	void __iomem *regs;
>  	unsigned int index;
> +	unsigned int irq;
>  
>  	struct reset_control *rst;
>  	struct clk *clk_parent;
> @@ -433,6 +436,11 @@ struct tegra_sor {
>  
>  	struct delayed_work scdc;
>  	bool scdc_enabled;
> +
> +	struct {
> +		unsigned int sample_rate;
> +		unsigned int channels;
> +	} audio;
>  };
>  
>  struct tegra_sor_state {
> @@ -2139,6 +2147,144 @@ tegra_sor_hdmi_setup_avi_infoframe(struct tegra_sor *sor,
>  	return 0;
>  }
>  
> +static void tegra_sor_write_eld(struct tegra_sor *sor)
> +{
> +	size_t length = drm_eld_size(sor->output.connector.eld), i;

This caught my eye, can't be right?

BR,
Jani.


> +
> +	for (i = 0; i < length; i++)
> +		tegra_sor_writel(sor, i << 8 | sor->output.connector.eld[i],
> +				 SOR_AUDIO_HDA_ELD_BUFWR);
> +
> +	/*
> +	 * The HDA codec will always report an ELD buffer size of 96 bytes and
> +	 * the HDA codec driver will check that each byte read from the buffer
> +	 * is valid. Therefore every byte must be written, even if no 96 bytes
> +	 * were parsed from EDID.
> +	 */
> +	for (i = length; i < 96; i++)
> +		tegra_sor_writel(sor, i << 8 | 0, SOR_AUDIO_HDA_ELD_BUFWR);
> +}
> +
> +static void tegra_sor_audio_prepare(struct tegra_sor *sor)
> +{
> +	u32 value;
> +
> +	tegra_sor_write_eld(sor);
> +
> +	value = SOR_AUDIO_HDA_PRESENSE_ELDV | SOR_AUDIO_HDA_PRESENSE_PD;
> +	tegra_sor_writel(sor, value, SOR_AUDIO_HDA_PRESENSE);
> +}
> +
> +static void tegra_sor_audio_unprepare(struct tegra_sor *sor)
> +{
> +	tegra_sor_writel(sor, 0, SOR_AUDIO_HDA_PRESENSE);
> +}
> +
> +static int tegra_sor_hdmi_enable_audio_infoframe(struct tegra_sor *sor)
> +{
> +	u8 buffer[HDMI_INFOFRAME_SIZE(AUDIO)];
> +	struct hdmi_audio_infoframe frame;
> +	u32 value;
> +	int err;
> +
> +	err = hdmi_audio_infoframe_init(&frame);
> +	if (err < 0) {
> +		dev_err(sor->dev, "failed to setup audio infoframe: %d\n", err);
> +		return err;
> +	}
> +
> +	frame.channels = sor->audio.channels;
> +
> +	err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer));
> +	if (err < 0) {
> +		dev_err(sor->dev, "failed to pack audio infoframe: %d\n", err);
> +		return err;
> +	}
> +
> +	tegra_sor_hdmi_write_infopack(sor, buffer, err);
> +
> +	value = tegra_sor_readl(sor, SOR_HDMI_AUDIO_INFOFRAME_CTRL);
> +	value |= INFOFRAME_CTRL_CHECKSUM_ENABLE;
> +	value |= INFOFRAME_CTRL_ENABLE;
> +	tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_INFOFRAME_CTRL);
> +
> +	return 0;
> +}
> +
> +static void tegra_sor_hdmi_audio_enable(struct tegra_sor *sor)
> +{
> +	u32 value;
> +
> +	value = tegra_sor_readl(sor, SOR_AUDIO_CNTRL);
> +
> +	/* select HDA audio input */
> +	value &= ~SOR_AUDIO_CNTRL_SOURCE_SELECT(SOURCE_SELECT_MASK);
> +	value |= SOR_AUDIO_CNTRL_SOURCE_SELECT(SOURCE_SELECT_HDA);
> +
> +	/* inject null samples */
> +	if (sor->audio.channels != 2)
> +		value &= ~SOR_AUDIO_CNTRL_INJECT_NULLSMPL;
> +	else
> +		value |= SOR_AUDIO_CNTRL_INJECT_NULLSMPL;
> +
> +	value |= SOR_AUDIO_CNTRL_AFIFO_FLUSH;
> +
> +	tegra_sor_writel(sor, value, SOR_AUDIO_CNTRL);
> +
> +	/* enable advertising HBR capability */
> +	tegra_sor_writel(sor, SOR_AUDIO_SPARE_HBR_ENABLE, SOR_AUDIO_SPARE);
> +
> +	tegra_sor_writel(sor, 0, SOR_HDMI_ACR_CTRL);
> +
> +	value = SOR_HDMI_SPARE_ACR_PRIORITY_HIGH |
> +		SOR_HDMI_SPARE_CTS_RESET(1) |
> +		SOR_HDMI_SPARE_HW_CTS_ENABLE;
> +	tegra_sor_writel(sor, value, SOR_HDMI_SPARE);
> +
> +	/* enable HW CTS */
> +	value = SOR_HDMI_ACR_SUBPACK_LOW_SB1(0);
> +	tegra_sor_writel(sor, value, SOR_HDMI_ACR_0441_SUBPACK_LOW);
> +
> +	/* allow packet to be sent */
> +	value = SOR_HDMI_ACR_SUBPACK_HIGH_ENABLE;
> +	tegra_sor_writel(sor, value, SOR_HDMI_ACR_0441_SUBPACK_HIGH);
> +
> +	/* reset N counter and enable lookup */
> +	value = SOR_HDMI_AUDIO_N_RESET | SOR_HDMI_AUDIO_N_LOOKUP;
> +	tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_N);
> +
> +	value = (24000 * 4096) / (128 * sor->audio.sample_rate / 1000);
> +	tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_0320);
> +	tegra_sor_writel(sor, 4096, SOR_AUDIO_NVAL_0320);
> +
> +	tegra_sor_writel(sor, 20000, SOR_AUDIO_AVAL_0441);
> +	tegra_sor_writel(sor, 4704, SOR_AUDIO_NVAL_0441);
> +
> +	tegra_sor_writel(sor, 20000, SOR_AUDIO_AVAL_0882);
> +	tegra_sor_writel(sor, 9408, SOR_AUDIO_NVAL_0882);
> +
> +	tegra_sor_writel(sor, 20000, SOR_AUDIO_AVAL_1764);
> +	tegra_sor_writel(sor, 18816, SOR_AUDIO_NVAL_1764);
> +
> +	value = (24000 * 6144) / (128 * sor->audio.sample_rate / 1000);
> +	tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_0480);
> +	tegra_sor_writel(sor, 6144, SOR_AUDIO_NVAL_0480);
> +
> +	value = (24000 * 12288) / (128 * sor->audio.sample_rate / 1000);
> +	tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_0960);
> +	tegra_sor_writel(sor, 12288, SOR_AUDIO_NVAL_0960);
> +
> +	value = (24000 * 24576) / (128 * sor->audio.sample_rate / 1000);
> +	tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_1920);
> +	tegra_sor_writel(sor, 24576, SOR_AUDIO_NVAL_1920);
> +
> +	value = tegra_sor_readl(sor, SOR_HDMI_AUDIO_N);
> +	value &= ~SOR_HDMI_AUDIO_N_RESET;
> +	tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_N);
> +
> +	tegra_sor_hdmi_enable_audio_infoframe(sor);
> +}
> +
>  static void tegra_sor_hdmi_disable_audio_infoframe(struct tegra_sor *sor)
>  {
>  	u32 value;
> @@ -2148,6 +2294,11 @@ static void tegra_sor_hdmi_disable_audio_infoframe(struct tegra_sor *sor)
>  	tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_INFOFRAME_CTRL);
>  }
>  
> +static void tegra_sor_hdmi_audio_disable(struct tegra_sor *sor)
> +{
> +	tegra_sor_hdmi_disable_audio_infoframe(sor);
> +}
> +
>  static struct tegra_sor_hdmi_settings *
>  tegra_sor_hdmi_find_settings(struct tegra_sor *sor, unsigned long frequency)
>  {
> @@ -2243,6 +2394,7 @@ static void tegra_sor_hdmi_disable(struct drm_encoder *encoder)
>  	u32 value;
>  	int err;
>  
> +	tegra_sor_audio_unprepare(sor);
>  	tegra_sor_hdmi_scdc_stop(sor);
>  
>  	err = tegra_sor_detach(sor);
> @@ -2651,6 +2803,7 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder)
>  		dev_err(sor->dev, "failed to wakeup SOR: %d\n", err);
>  
>  	tegra_sor_hdmi_scdc_start(sor);
> +	tegra_sor_audio_prepare(sor);
>  }
>  
>  static const struct drm_encoder_helper_funcs tegra_sor_hdmi_helpers = {
> @@ -2666,6 +2819,7 @@ static int tegra_sor_init(struct host1x_client *client)
>  	struct tegra_sor *sor = host1x_client_to_sor(client);
>  	int connector = DRM_MODE_CONNECTOR_Unknown;
>  	int encoder = DRM_MODE_ENCODER_NONE;
> +	u32 value;
>  	int err;
>  
>  	if (!sor->aux) {
> @@ -2759,6 +2913,15 @@ static int tegra_sor_init(struct host1x_client *client)
>  	if (err < 0)
>  		return err;
>  
> +	/*
> +	 * Enable and unmask the HDA codec SCRATCH0 register interrupt. This
> +	 * is used for interoperability between the HDA codec driver and the
> +	 * HDMI/DP driver.
> +	 */
> +	value = SOR_INT_CODEC_SCRATCH1 | SOR_INT_CODEC_SCRATCH0;
> +	tegra_sor_writel(sor, value, SOR_INT_ENABLE);
> +	tegra_sor_writel(sor, value, SOR_INT_MASK);
> +
>  	return 0;
>  }
>  
> @@ -2767,6 +2930,9 @@ static int tegra_sor_exit(struct host1x_client *client)
>  	struct tegra_sor *sor = host1x_client_to_sor(client);
>  	int err;
>  
> +	tegra_sor_writel(sor, 0, SOR_INT_MASK);
> +	tegra_sor_writel(sor, 0, SOR_INT_ENABLE);
> +
>  	tegra_output_exit(&sor->output);
>  
>  	if (sor->aux) {
> @@ -3037,6 +3203,54 @@ static int tegra_sor_parse_dt(struct tegra_sor *sor)
>  	return 0;
>  }
>  
> +static void tegra_hda_parse_format(unsigned int format, unsigned int *rate,
> +				   unsigned int *channels)
> +{
> +	unsigned int mul, div;
> +
> +	if (format & AC_FMT_BASE_44K)
> +		*rate = 44100;
> +	else
> +		*rate = 48000;
> +
> +	mul = (format & AC_FMT_MULT_MASK) >> AC_FMT_MULT_SHIFT;
> +	div = (format & AC_FMT_DIV_MASK) >> AC_FMT_DIV_SHIFT;
> +
> +	*rate = *rate * (mul + 1) / (div + 1);
> +
> +	*channels = (format & AC_FMT_CHAN_MASK) >> AC_FMT_CHAN_SHIFT;
> +}
> +
> +static irqreturn_t tegra_sor_irq(int irq, void *data)
> +{
> +	struct tegra_sor *sor = data;
> +	u32 value;
> +
> +	value = tegra_sor_readl(sor, SOR_INT_STATUS);
> +	tegra_sor_writel(sor, value, SOR_INT_STATUS);
> +
> +	if (value & SOR_INT_CODEC_SCRATCH0) {
> +		value = tegra_sor_readl(sor, SOR_AUDIO_HDA_CODEC_SCRATCH0);
> +
> +		if (value & SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID) {
> +			unsigned int format, sample_rate, channels;
> +
> +			format = value & SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK;
> +
> +			tegra_hda_parse_format(format, &sample_rate, &channels);
> +
> +			sor->audio.sample_rate = sample_rate;
> +			sor->audio.channels = channels;
> +
> +			tegra_sor_hdmi_audio_enable(sor);
> +		} else {
> +			tegra_sor_hdmi_audio_disable(sor);
> +		}
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
>  static int tegra_sor_probe(struct platform_device *pdev)
>  {
>  	struct device_node *np;
> @@ -3119,6 +3333,21 @@ static int tegra_sor_probe(struct platform_device *pdev)
>  		goto remove;
>  	}
>  
> +	err = platform_get_irq(pdev, 0);
> +	if (err < 0) {
> +		dev_err(&pdev->dev, "failed to get IRQ: %d\n", err);
> +		goto remove;
> +	}
> +
> +	sor->irq = err;
> +
> +	err = devm_request_irq(sor->dev, sor->irq, tegra_sor_irq, 0,
> +			       dev_name(sor->dev), sor);
> +	if (err < 0) {
> +		dev_err(&pdev->dev, "failed to request IRQ: %d\n", err);
> +		goto remove;
> +	}
> +
>  	if (!pdev->dev.pm_domain) {
>  		sor->rst = devm_reset_control_get(&pdev->dev, "sor");
>  		if (IS_ERR(sor->rst)) {
> diff --git a/drivers/gpu/drm/tegra/sor.h b/drivers/gpu/drm/tegra/sor.h
> index fb0854d92a27..13f7e68bec42 100644
> --- a/drivers/gpu/drm/tegra/sor.h
> +++ b/drivers/gpu/drm/tegra/sor.h
> @@ -364,12 +364,28 @@
>  #define  INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) << 8)
>  #define  INFOFRAME_HEADER_TYPE(x) (((x) & 0xff) << 0)
>  
> +#define SOR_HDMI_ACR_CTRL 0xb1
> +
> +#define SOR_HDMI_ACR_0320_SUBPACK_LOW 0xb2
> +#define  SOR_HDMI_ACR_SUBPACK_LOW_SB1(x) (((x) & 0xff) << 24)
> +
> +#define SOR_HDMI_ACR_0320_SUBPACK_HIGH 0xb3
> +#define  SOR_HDMI_ACR_SUBPACK_HIGH_ENABLE (1 << 31)
> +
> +#define SOR_HDMI_ACR_0441_SUBPACK_LOW 0xb4
> +#define SOR_HDMI_ACR_0441_SUBPACK_HIGH 0xb5
> +
>  #define SOR_HDMI_CTRL 0xc0
>  #define  SOR_HDMI_CTRL_ENABLE (1 << 30)
>  #define  SOR_HDMI_CTRL_MAX_AC_PACKET(x) (((x) & 0x1f) << 16)
>  #define  SOR_HDMI_CTRL_AUDIO_LAYOUT (1 << 10)
>  #define  SOR_HDMI_CTRL_REKEY(x) (((x) & 0x7f) << 0)
>  
> +#define SOR_HDMI_SPARE 0xcb
> +#define  SOR_HDMI_SPARE_ACR_PRIORITY_HIGH (1 << 31)
> +#define  SOR_HDMI_SPARE_CTS_RESET(x) (((x) & 0x7) << 16)
> +#define  SOR_HDMI_SPARE_HW_CTS_ENABLE (1 << 0)
> +
>  #define SOR_REFCLK 0xe6
>  #define  SOR_REFCLK_DIV_INT(x) ((((x) >> 2) & 0xff) << 8)
>  #define  SOR_REFCLK_DIV_FRAC(x) (((x) & 0x3) << 6)
> @@ -378,10 +394,62 @@
>  #define  SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_LIMITED (1 << 1)
>  #define  SOR_INPUT_CONTROL_HDMI_SRC_SELECT(x) (((x) & 0x1) << 0)
>  
> +#define SOR_AUDIO_CNTRL 0xfc
> +#define  SOR_AUDIO_CNTRL_INJECT_NULLSMPL (1 << 29)
> +#define  SOR_AUDIO_CNTRL_SOURCE_SELECT(x) (((x) & 0x3) << 20)
> +#define   SOURCE_SELECT_MASK 0x3
> +#define   SOURCE_SELECT_HDA 0x2
> +#define   SOURCE_SELECT_SPDIF 0x1
> +#define   SOURCE_SELECT_AUTO 0x0
> +#define  SOR_AUDIO_CNTRL_AFIFO_FLUSH (1 << 12)
> +
> +#define SOR_AUDIO_SPARE 0xfe
> +#define  SOR_AUDIO_SPARE_HBR_ENABLE (1 << 27)
> +
> +#define SOR_AUDIO_NVAL_0320 0xff
> +#define SOR_AUDIO_NVAL_0441 0x100
> +#define SOR_AUDIO_NVAL_0882 0x101
> +#define SOR_AUDIO_NVAL_1764 0x102
> +#define SOR_AUDIO_NVAL_0480 0x103
> +#define SOR_AUDIO_NVAL_0960 0x104
> +#define SOR_AUDIO_NVAL_1920 0x105
> +
> +#define SOR_AUDIO_HDA_CODEC_SCRATCH0 0x10a
> +#define  SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID (1 << 30)
> +#define  SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK 0xffff
> +
> +#define SOR_AUDIO_HDA_ELD_BUFWR 0x10c
> +#define  SOR_AUDIO_HDA_ELD_BUFWR_INDEX(x) (((x) & 0xff) << 8)
> +#define  SOR_AUDIO_HDA_ELD_BUFWR_DATA(x) (((x) & 0xff) << 0)
> +
> +#define SOR_AUDIO_HDA_PRESENSE 0x10d
> +#define  SOR_AUDIO_HDA_PRESENSE_ELDV (1 << 1)
> +#define  SOR_AUDIO_HDA_PRESENSE_PD (1 << 0)
> +
> +#define SOR_AUDIO_AVAL_0320 0x10f
> +#define SOR_AUDIO_AVAL_0441 0x110
> +#define SOR_AUDIO_AVAL_0882 0x111
> +#define SOR_AUDIO_AVAL_1764 0x112
> +#define SOR_AUDIO_AVAL_0480 0x113
> +#define SOR_AUDIO_AVAL_0960 0x114
> +#define SOR_AUDIO_AVAL_1920 0x115
> +
> +#define SOR_INT_STATUS 0x11c
> +#define  SOR_INT_CODEC_CP_REQUEST (1 << 2)
> +#define  SOR_INT_CODEC_SCRATCH1 (1 << 1)
> +#define  SOR_INT_CODEC_SCRATCH0 (1 << 0)
> +
> +#define SOR_INT_MASK 0x11d
> +#define SOR_INT_ENABLE 0x11e
> +
>  #define SOR_HDMI_VSI_INFOFRAME_CTRL 0x123
>  #define SOR_HDMI_VSI_INFOFRAME_STATUS 0x124
>  #define SOR_HDMI_VSI_INFOFRAME_HEADER 0x125
>  
> +#define SOR_HDMI_AUDIO_N 0x13c
> +#define SOR_HDMI_AUDIO_N_LOOKUP (1 << 28)
> +#define SOR_HDMI_AUDIO_N_RESET (1 << 20)
> +
>  #define SOR_HDMI2_CTRL 0x13e
>  #define  SOR_HDMI2_CTRL_CLOCK_MODE_DIV_BY_4 (1 << 1)
>  #define  SOR_HDMI2_CTRL_SCRAMBLE (1 << 0)
Thierry Reding Dec. 4, 2018, 8:42 a.m. UTC | #2
On Mon, Dec 03, 2018 at 06:05:07PM +0200, Jani Nikula wrote:
> On Mon, 03 Dec 2018, Thierry Reding <thierry.reding@gmail.com> wrote:
> > From: Thierry Reding <treding@nvidia.com>
> >
> > This code is very similar to the audio over HDMI support on older chips.
> > Interoperation with the audio codec is done via a pair of codec scratch
> > registers and an interrupt that is raised at the SOR when the codec has
> > written those registers.
> >
> > Signed-off-by: Thierry Reding <treding@nvidia.com>
> > ---
> >  drivers/gpu/drm/tegra/sor.c | 229 ++++++++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/tegra/sor.h |  68 +++++++++++
> >  2 files changed, 297 insertions(+)
> >
> > diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c
> > index b129da2e5afd..22a54434a757 100644
> > --- a/drivers/gpu/drm/tegra/sor.c
> > +++ b/drivers/gpu/drm/tegra/sor.c
> > @@ -19,6 +19,8 @@
> >  
> >  #include <soc/tegra/pmc.h>
> >  
> > +#include <sound/hda_verbs.h>
> > +
> >  #include <drm/drm_atomic_helper.h>
> >  #include <drm/drm_dp_helper.h>
> >  #include <drm/drm_panel.h>
> > @@ -407,6 +409,7 @@ struct tegra_sor {
> >  	const struct tegra_sor_soc *soc;
> >  	void __iomem *regs;
> >  	unsigned int index;
> > +	unsigned int irq;
> >  
> >  	struct reset_control *rst;
> >  	struct clk *clk_parent;
> > @@ -433,6 +436,11 @@ struct tegra_sor {
> >  
> >  	struct delayed_work scdc;
> >  	bool scdc_enabled;
> > +
> > +	struct {
> > +		unsigned int sample_rate;
> > +		unsigned int channels;
> > +	} audio;
> >  };
> >  
> >  struct tegra_sor_state {
> > @@ -2139,6 +2147,144 @@ tegra_sor_hdmi_setup_avi_infoframe(struct tegra_sor *sor,
> >  	return 0;
> >  }
> >  
> > +static void tegra_sor_write_eld(struct tegra_sor *sor)
> > +{
> > +	size_t length = drm_eld_size(sor->output.connector.eld), i;
> 
> This caught my eye, can't be right?

Why do you think it's wrong? The length is the number of bytes that are
to be written to the HDA ELD buffer. This is pretty much the same across
all drivers that support HDMI audio (i915 and nouveau).

Also, audio is definitely working with this patch, and the HDMI codecs
are showing the correct information in procfs.

Can you elaborate?

Thierry
Jani Nikula Dec. 4, 2018, 9:33 a.m. UTC | #3
On Tue, 04 Dec 2018, Thierry Reding <thierry.reding@gmail.com> wrote:
> On Mon, Dec 03, 2018 at 06:05:07PM +0200, Jani Nikula wrote:
>> On Mon, 03 Dec 2018, Thierry Reding <thierry.reding@gmail.com> wrote:
>> > From: Thierry Reding <treding@nvidia.com>
>> >
>> > This code is very similar to the audio over HDMI support on older chips.
>> > Interoperation with the audio codec is done via a pair of codec scratch
>> > registers and an interrupt that is raised at the SOR when the codec has
>> > written those registers.
>> >
>> > Signed-off-by: Thierry Reding <treding@nvidia.com>
>> > ---
>> >  drivers/gpu/drm/tegra/sor.c | 229 ++++++++++++++++++++++++++++++++++++
>> >  drivers/gpu/drm/tegra/sor.h |  68 +++++++++++
>> >  2 files changed, 297 insertions(+)
>> >
>> > diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c
>> > index b129da2e5afd..22a54434a757 100644
>> > --- a/drivers/gpu/drm/tegra/sor.c
>> > +++ b/drivers/gpu/drm/tegra/sor.c
>> > @@ -19,6 +19,8 @@
>> >  
>> >  #include <soc/tegra/pmc.h>
>> >  
>> > +#include <sound/hda_verbs.h>
>> > +
>> >  #include <drm/drm_atomic_helper.h>
>> >  #include <drm/drm_dp_helper.h>
>> >  #include <drm/drm_panel.h>
>> > @@ -407,6 +409,7 @@ struct tegra_sor {
>> >  	const struct tegra_sor_soc *soc;
>> >  	void __iomem *regs;
>> >  	unsigned int index;
>> > +	unsigned int irq;
>> >  
>> >  	struct reset_control *rst;
>> >  	struct clk *clk_parent;
>> > @@ -433,6 +436,11 @@ struct tegra_sor {
>> >  
>> >  	struct delayed_work scdc;
>> >  	bool scdc_enabled;
>> > +
>> > +	struct {
>> > +		unsigned int sample_rate;
>> > +		unsigned int channels;
>> > +	} audio;
>> >  };
>> >  
>> >  struct tegra_sor_state {
>> > @@ -2139,6 +2147,144 @@ tegra_sor_hdmi_setup_avi_infoframe(struct tegra_sor *sor,
>> >  	return 0;
>> >  }
>> >  
>> > +static void tegra_sor_write_eld(struct tegra_sor *sor)
>> > +{
>> > +	size_t length = drm_eld_size(sor->output.connector.eld), i;
>> 
>> This caught my eye, can't be right?
>
> Why do you think it's wrong? The length is the number of bytes that are
> to be written to the HDA ELD buffer. This is pretty much the same across
> all drivers that support HDMI audio (i915 and nouveau).
>
> Also, audio is definitely working with this patch, and the HDMI codecs
> are showing the correct information in procfs.
>
> Can you elaborate?

Ugh, apologies for being too terse in my reply, as well as utterly
wrong, and also too ashamed to admit after the fact where my fallacy
was!

BR,
Jani.
Thierry Reding Dec. 4, 2018, 10:09 a.m. UTC | #4
On Tue, Dec 04, 2018 at 11:33:48AM +0200, Jani Nikula wrote:
> On Tue, 04 Dec 2018, Thierry Reding <thierry.reding@gmail.com> wrote:
> > On Mon, Dec 03, 2018 at 06:05:07PM +0200, Jani Nikula wrote:
> >> On Mon, 03 Dec 2018, Thierry Reding <thierry.reding@gmail.com> wrote:
> >> > From: Thierry Reding <treding@nvidia.com>
> >> >
> >> > This code is very similar to the audio over HDMI support on older chips.
> >> > Interoperation with the audio codec is done via a pair of codec scratch
> >> > registers and an interrupt that is raised at the SOR when the codec has
> >> > written those registers.
> >> >
> >> > Signed-off-by: Thierry Reding <treding@nvidia.com>
> >> > ---
> >> >  drivers/gpu/drm/tegra/sor.c | 229 ++++++++++++++++++++++++++++++++++++
> >> >  drivers/gpu/drm/tegra/sor.h |  68 +++++++++++
> >> >  2 files changed, 297 insertions(+)
> >> >
> >> > diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c
> >> > index b129da2e5afd..22a54434a757 100644
> >> > --- a/drivers/gpu/drm/tegra/sor.c
> >> > +++ b/drivers/gpu/drm/tegra/sor.c
> >> > @@ -19,6 +19,8 @@
> >> >  
> >> >  #include <soc/tegra/pmc.h>
> >> >  
> >> > +#include <sound/hda_verbs.h>
> >> > +
> >> >  #include <drm/drm_atomic_helper.h>
> >> >  #include <drm/drm_dp_helper.h>
> >> >  #include <drm/drm_panel.h>
> >> > @@ -407,6 +409,7 @@ struct tegra_sor {
> >> >  	const struct tegra_sor_soc *soc;
> >> >  	void __iomem *regs;
> >> >  	unsigned int index;
> >> > +	unsigned int irq;
> >> >  
> >> >  	struct reset_control *rst;
> >> >  	struct clk *clk_parent;
> >> > @@ -433,6 +436,11 @@ struct tegra_sor {
> >> >  
> >> >  	struct delayed_work scdc;
> >> >  	bool scdc_enabled;
> >> > +
> >> > +	struct {
> >> > +		unsigned int sample_rate;
> >> > +		unsigned int channels;
> >> > +	} audio;
> >> >  };
> >> >  
> >> >  struct tegra_sor_state {
> >> > @@ -2139,6 +2147,144 @@ tegra_sor_hdmi_setup_avi_infoframe(struct tegra_sor *sor,
> >> >  	return 0;
> >> >  }
> >> >  
> >> > +static void tegra_sor_write_eld(struct tegra_sor *sor)
> >> > +{
> >> > +	size_t length = drm_eld_size(sor->output.connector.eld), i;
> >> 
> >> This caught my eye, can't be right?
> >
> > Why do you think it's wrong? The length is the number of bytes that are
> > to be written to the HDA ELD buffer. This is pretty much the same across
> > all drivers that support HDMI audio (i915 and nouveau).
> >
> > Also, audio is definitely working with this patch, and the HDMI codecs
> > are showing the correct information in procfs.
> >
> > Can you elaborate?
> 
> Ugh, apologies for being too terse in my reply, as well as utterly
> wrong, and also too ashamed to admit after the fact where my fallacy
> was!

No worries.

Thierry
Dmitry Osipenko Dec. 4, 2018, 11:09 a.m. UTC | #5
On 03.12.2018 18:36, Thierry Reding wrote:
> From: Thierry Reding <treding@nvidia.com>
> 
> This code is very similar to the audio over HDMI support on older chips.
> Interoperation with the audio codec is done via a pair of codec scratch
> registers and an interrupt that is raised at the SOR when the codec has
> written those registers.
> 
> Signed-off-by: Thierry Reding <treding@nvidia.com>
> ---

Do you have any plans to implement integration with the sound subsystem? Indeed, there is HDMI audio configuration code for older chips in the Tegra's DRM driver that was added years ago.. but IIUC it's a kinda "dead code" without the integration.
Thierry Reding Dec. 4, 2018, 1:08 p.m. UTC | #6
On Tue, Dec 04, 2018 at 02:09:07PM +0300, Dmitry Osipenko wrote:
> On 03.12.2018 18:36, Thierry Reding wrote:
> > From: Thierry Reding <treding@nvidia.com>
> > 
> > This code is very similar to the audio over HDMI support on older chips.
> > Interoperation with the audio codec is done via a pair of codec scratch
> > registers and an interrupt that is raised at the SOR when the codec has
> > written those registers.
> > 
> > Signed-off-by: Thierry Reding <treding@nvidia.com>
> > ---
> 
> Do you have any plans to implement integration with the sound
> subsystem? Indeed, there is HDMI audio configuration code for older
> chips in the Tegra's DRM driver that was added years ago.. but IIUC
> it's a kinda "dead code" without the integration.

The integration all lives in sound/pci/hda/hda_tegra.c for the HDA
controller driver and sound/pci/hda/patch_hdmi.c for the HDMI codec
driver. The way that this works is that the HDMI codec driver writes
information about the audio format to so-called scratch registers via
HDA verbs. These HDA verbs are accessible in the SORs (or the HDMI on
older Tegra) which basically represent the HDMI codec. Writes to these
registers are detected and an interrupt is raised in the SOR (or HDMI)
controller, upon which the interrupt handler will configure the output
as needed for playback (i.e. program some registers, send out audio
infoframe, ...).

From a userspace point of view you can simply access the HDMI codec as
an ALSA sound card. For example I use the speaker-test utility (from
alsa-utils) to test output like this:

	$ speaker-test -D hw:0,8 -c -F S16_LE -r 48000 -t sine -f 250 -l 1

The same code that works from Tegra30 to Tegra210 also works on Tegra186
and Tegra194, though I have patches (which I plan to send out later
today) which add the HDA_CODEC_ENTRY entries as well as the necessary
device tree nodes to make all this work.

Does that clarify things?

Thierry
Dmitry Osipenko Dec. 4, 2018, 1:50 p.m. UTC | #7
On 04.12.2018 16:08, Thierry Reding wrote:
> On Tue, Dec 04, 2018 at 02:09:07PM +0300, Dmitry Osipenko wrote:
>> On 03.12.2018 18:36, Thierry Reding wrote:
>>> From: Thierry Reding <treding@nvidia.com>
>>>
>>> This code is very similar to the audio over HDMI support on older chips.
>>> Interoperation with the audio codec is done via a pair of codec scratch
>>> registers and an interrupt that is raised at the SOR when the codec has
>>> written those registers.
>>>
>>> Signed-off-by: Thierry Reding <treding@nvidia.com>
>>> ---
>>
>> Do you have any plans to implement integration with the sound
>> subsystem? Indeed, there is HDMI audio configuration code for older
>> chips in the Tegra's DRM driver that was added years ago.. but IIUC
>> it's a kinda "dead code" without the integration.
> 
> The integration all lives in sound/pci/hda/hda_tegra.c for the HDA
> controller driver and sound/pci/hda/patch_hdmi.c for the HDMI codec
> driver. The way that this works is that the HDMI codec driver writes
> information about the audio format to so-called scratch registers via
> HDA verbs. These HDA verbs are accessible in the SORs (or the HDMI on
> older Tegra) which basically represent the HDMI codec. Writes to these
> registers are detected and an interrupt is raised in the SOR (or HDMI)
> controller, upon which the interrupt handler will configure the output
> as needed for playback (i.e. program some registers, send out audio
> infoframe, ...).
> 
> From a userspace point of view you can simply access the HDMI codec as
> an ALSA sound card. For example I use the speaker-test utility (from
> alsa-utils) to test output like this:
> 
> 	$ speaker-test -D hw:0,8 -c -F S16_LE -r 48000 -t sine -f 250 -l 1
> 
> The same code that works from Tegra30 to Tegra210 also works on Tegra186
> and Tegra194, though I have patches (which I plan to send out later
> today) which add the HDA_CODEC_ENTRY entries as well as the necessary
> device tree nodes to make all this work.
> 
> Does that clarify things?

Yes, that sounds like a nice way to integrate HW. I was also thinking from a good-old T20 perspective which AFAIK doesn't have that kind of HW integration, so was curious how it works for later Tegra's. Thank you very much for the explanation. Though it shouldn't be difficult to add SW integration with the audio codec.. but probably there was just no much need for it ever. It's good to know that it works for T30+, please keep up the good work :)
Thierry Reding Dec. 4, 2018, 2:40 p.m. UTC | #8
On Tue, Dec 04, 2018 at 04:50:44PM +0300, Dmitry Osipenko wrote:
> On 04.12.2018 16:08, Thierry Reding wrote:
> > On Tue, Dec 04, 2018 at 02:09:07PM +0300, Dmitry Osipenko wrote:
> >> On 03.12.2018 18:36, Thierry Reding wrote:
> >>> From: Thierry Reding <treding@nvidia.com>
> >>>
> >>> This code is very similar to the audio over HDMI support on older chips.
> >>> Interoperation with the audio codec is done via a pair of codec scratch
> >>> registers and an interrupt that is raised at the SOR when the codec has
> >>> written those registers.
> >>>
> >>> Signed-off-by: Thierry Reding <treding@nvidia.com>
> >>> ---
> >>
> >> Do you have any plans to implement integration with the sound
> >> subsystem? Indeed, there is HDMI audio configuration code for older
> >> chips in the Tegra's DRM driver that was added years ago.. but IIUC
> >> it's a kinda "dead code" without the integration.
> > 
> > The integration all lives in sound/pci/hda/hda_tegra.c for the HDA
> > controller driver and sound/pci/hda/patch_hdmi.c for the HDMI codec
> > driver. The way that this works is that the HDMI codec driver writes
> > information about the audio format to so-called scratch registers via
> > HDA verbs. These HDA verbs are accessible in the SORs (or the HDMI on
> > older Tegra) which basically represent the HDMI codec. Writes to these
> > registers are detected and an interrupt is raised in the SOR (or HDMI)
> > controller, upon which the interrupt handler will configure the output
> > as needed for playback (i.e. program some registers, send out audio
> > infoframe, ...).
> > 
> > From a userspace point of view you can simply access the HDMI codec as
> > an ALSA sound card. For example I use the speaker-test utility (from
> > alsa-utils) to test output like this:
> > 
> > 	$ speaker-test -D hw:0,8 -c -F S16_LE -r 48000 -t sine -f 250 -l 1
> > 
> > The same code that works from Tegra30 to Tegra210 also works on Tegra186
> > and Tegra194, though I have patches (which I plan to send out later
> > today) which add the HDA_CODEC_ENTRY entries as well as the necessary
> > device tree nodes to make all this work.
> > 
> > Does that clarify things?
> 
> Yes, that sounds like a nice way to integrate HW. I was also thinking
> from a good-old T20 perspective which AFAIK doesn't have that kind of
> HW integration, so was curious how it works for later Tegra's. Thank
> you very much for the explanation. Though it shouldn't be difficult to
> add SW integration with the audio codec.. but probably there was just
> no much need for it ever. It's good to know that it works for T30+,
> please keep up the good work :)

Yeah, downstream was (and still is I think) using a pure SW integration,
but it requires a custom API and then complicated code to look up the
correct SOR for a given HDMI codec and vice versa. Doing it via the HDA
codec scratch registers is what was originally intended from the design
teams and removes the need for any hacks.

For Tegra20 the situation is completely different because it doesn't
even have HDA, so we'd need to hook up one of the SPDIF outputs to HDMI
if we wanted to support HDMI audio there. I suspect that there must be
existing mechanisms to do this for platform devices, given that there
must be other devices that support HDMI audio and that don't integrate
tightly with an HDA codec.

Thierry
diff mbox series

Patch

diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c
index b129da2e5afd..22a54434a757 100644
--- a/drivers/gpu/drm/tegra/sor.c
+++ b/drivers/gpu/drm/tegra/sor.c
@@ -19,6 +19,8 @@ 
 
 #include <soc/tegra/pmc.h>
 
+#include <sound/hda_verbs.h>
+
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_dp_helper.h>
 #include <drm/drm_panel.h>
@@ -407,6 +409,7 @@  struct tegra_sor {
 	const struct tegra_sor_soc *soc;
 	void __iomem *regs;
 	unsigned int index;
+	unsigned int irq;
 
 	struct reset_control *rst;
 	struct clk *clk_parent;
@@ -433,6 +436,11 @@  struct tegra_sor {
 
 	struct delayed_work scdc;
 	bool scdc_enabled;
+
+	struct {
+		unsigned int sample_rate;
+		unsigned int channels;
+	} audio;
 };
 
 struct tegra_sor_state {
@@ -2139,6 +2147,144 @@  tegra_sor_hdmi_setup_avi_infoframe(struct tegra_sor *sor,
 	return 0;
 }
 
+static void tegra_sor_write_eld(struct tegra_sor *sor)
+{
+	size_t length = drm_eld_size(sor->output.connector.eld), i;
+
+	for (i = 0; i < length; i++)
+		tegra_sor_writel(sor, i << 8 | sor->output.connector.eld[i],
+				 SOR_AUDIO_HDA_ELD_BUFWR);
+
+	/*
+	 * The HDA codec will always report an ELD buffer size of 96 bytes and
+	 * the HDA codec driver will check that each byte read from the buffer
+	 * is valid. Therefore every byte must be written, even if no 96 bytes
+	 * were parsed from EDID.
+	 */
+	for (i = length; i < 96; i++)
+		tegra_sor_writel(sor, i << 8 | 0, SOR_AUDIO_HDA_ELD_BUFWR);
+}
+
+static void tegra_sor_audio_prepare(struct tegra_sor *sor)
+{
+	u32 value;
+
+	tegra_sor_write_eld(sor);
+
+	value = SOR_AUDIO_HDA_PRESENSE_ELDV | SOR_AUDIO_HDA_PRESENSE_PD;
+	tegra_sor_writel(sor, value, SOR_AUDIO_HDA_PRESENSE);
+}
+
+static void tegra_sor_audio_unprepare(struct tegra_sor *sor)
+{
+	tegra_sor_writel(sor, 0, SOR_AUDIO_HDA_PRESENSE);
+}
+
+static int tegra_sor_hdmi_enable_audio_infoframe(struct tegra_sor *sor)
+{
+	u8 buffer[HDMI_INFOFRAME_SIZE(AUDIO)];
+	struct hdmi_audio_infoframe frame;
+	u32 value;
+	int err;
+
+	err = hdmi_audio_infoframe_init(&frame);
+	if (err < 0) {
+		dev_err(sor->dev, "failed to setup audio infoframe: %d\n", err);
+		return err;
+	}
+
+	frame.channels = sor->audio.channels;
+
+	err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer));
+	if (err < 0) {
+		dev_err(sor->dev, "failed to pack audio infoframe: %d\n", err);
+		return err;
+	}
+
+	tegra_sor_hdmi_write_infopack(sor, buffer, err);
+
+	value = tegra_sor_readl(sor, SOR_HDMI_AUDIO_INFOFRAME_CTRL);
+	value |= INFOFRAME_CTRL_CHECKSUM_ENABLE;
+	value |= INFOFRAME_CTRL_ENABLE;
+	tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_INFOFRAME_CTRL);
+
+	return 0;
+}
+
+static void tegra_sor_hdmi_audio_enable(struct tegra_sor *sor)
+{
+	u32 value;
+
+	value = tegra_sor_readl(sor, SOR_AUDIO_CNTRL);
+
+	/* select HDA audio input */
+	value &= ~SOR_AUDIO_CNTRL_SOURCE_SELECT(SOURCE_SELECT_MASK);
+	value |= SOR_AUDIO_CNTRL_SOURCE_SELECT(SOURCE_SELECT_HDA);
+
+	/* inject null samples */
+	if (sor->audio.channels != 2)
+		value &= ~SOR_AUDIO_CNTRL_INJECT_NULLSMPL;
+	else
+		value |= SOR_AUDIO_CNTRL_INJECT_NULLSMPL;
+
+	value |= SOR_AUDIO_CNTRL_AFIFO_FLUSH;
+
+	tegra_sor_writel(sor, value, SOR_AUDIO_CNTRL);
+
+	/* enable advertising HBR capability */
+	tegra_sor_writel(sor, SOR_AUDIO_SPARE_HBR_ENABLE, SOR_AUDIO_SPARE);
+
+	tegra_sor_writel(sor, 0, SOR_HDMI_ACR_CTRL);
+
+	value = SOR_HDMI_SPARE_ACR_PRIORITY_HIGH |
+		SOR_HDMI_SPARE_CTS_RESET(1) |
+		SOR_HDMI_SPARE_HW_CTS_ENABLE;
+	tegra_sor_writel(sor, value, SOR_HDMI_SPARE);
+
+	/* enable HW CTS */
+	value = SOR_HDMI_ACR_SUBPACK_LOW_SB1(0);
+	tegra_sor_writel(sor, value, SOR_HDMI_ACR_0441_SUBPACK_LOW);
+
+	/* allow packet to be sent */
+	value = SOR_HDMI_ACR_SUBPACK_HIGH_ENABLE;
+	tegra_sor_writel(sor, value, SOR_HDMI_ACR_0441_SUBPACK_HIGH);
+
+	/* reset N counter and enable lookup */
+	value = SOR_HDMI_AUDIO_N_RESET | SOR_HDMI_AUDIO_N_LOOKUP;
+	tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_N);
+
+	value = (24000 * 4096) / (128 * sor->audio.sample_rate / 1000);
+	tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_0320);
+	tegra_sor_writel(sor, 4096, SOR_AUDIO_NVAL_0320);
+
+	tegra_sor_writel(sor, 20000, SOR_AUDIO_AVAL_0441);
+	tegra_sor_writel(sor, 4704, SOR_AUDIO_NVAL_0441);
+
+	tegra_sor_writel(sor, 20000, SOR_AUDIO_AVAL_0882);
+	tegra_sor_writel(sor, 9408, SOR_AUDIO_NVAL_0882);
+
+	tegra_sor_writel(sor, 20000, SOR_AUDIO_AVAL_1764);
+	tegra_sor_writel(sor, 18816, SOR_AUDIO_NVAL_1764);
+
+	value = (24000 * 6144) / (128 * sor->audio.sample_rate / 1000);
+	tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_0480);
+	tegra_sor_writel(sor, 6144, SOR_AUDIO_NVAL_0480);
+
+	value = (24000 * 12288) / (128 * sor->audio.sample_rate / 1000);
+	tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_0960);
+	tegra_sor_writel(sor, 12288, SOR_AUDIO_NVAL_0960);
+
+	value = (24000 * 24576) / (128 * sor->audio.sample_rate / 1000);
+	tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_1920);
+	tegra_sor_writel(sor, 24576, SOR_AUDIO_NVAL_1920);
+
+	value = tegra_sor_readl(sor, SOR_HDMI_AUDIO_N);
+	value &= ~SOR_HDMI_AUDIO_N_RESET;
+	tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_N);
+
+	tegra_sor_hdmi_enable_audio_infoframe(sor);
+}
+
 static void tegra_sor_hdmi_disable_audio_infoframe(struct tegra_sor *sor)
 {
 	u32 value;
@@ -2148,6 +2294,11 @@  static void tegra_sor_hdmi_disable_audio_infoframe(struct tegra_sor *sor)
 	tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_INFOFRAME_CTRL);
 }
 
+static void tegra_sor_hdmi_audio_disable(struct tegra_sor *sor)
+{
+	tegra_sor_hdmi_disable_audio_infoframe(sor);
+}
+
 static struct tegra_sor_hdmi_settings *
 tegra_sor_hdmi_find_settings(struct tegra_sor *sor, unsigned long frequency)
 {
@@ -2243,6 +2394,7 @@  static void tegra_sor_hdmi_disable(struct drm_encoder *encoder)
 	u32 value;
 	int err;
 
+	tegra_sor_audio_unprepare(sor);
 	tegra_sor_hdmi_scdc_stop(sor);
 
 	err = tegra_sor_detach(sor);
@@ -2651,6 +2803,7 @@  static void tegra_sor_hdmi_enable(struct drm_encoder *encoder)
 		dev_err(sor->dev, "failed to wakeup SOR: %d\n", err);
 
 	tegra_sor_hdmi_scdc_start(sor);
+	tegra_sor_audio_prepare(sor);
 }
 
 static const struct drm_encoder_helper_funcs tegra_sor_hdmi_helpers = {
@@ -2666,6 +2819,7 @@  static int tegra_sor_init(struct host1x_client *client)
 	struct tegra_sor *sor = host1x_client_to_sor(client);
 	int connector = DRM_MODE_CONNECTOR_Unknown;
 	int encoder = DRM_MODE_ENCODER_NONE;
+	u32 value;
 	int err;
 
 	if (!sor->aux) {
@@ -2759,6 +2913,15 @@  static int tegra_sor_init(struct host1x_client *client)
 	if (err < 0)
 		return err;
 
+	/*
+	 * Enable and unmask the HDA codec SCRATCH0 register interrupt. This
+	 * is used for interoperability between the HDA codec driver and the
+	 * HDMI/DP driver.
+	 */
+	value = SOR_INT_CODEC_SCRATCH1 | SOR_INT_CODEC_SCRATCH0;
+	tegra_sor_writel(sor, value, SOR_INT_ENABLE);
+	tegra_sor_writel(sor, value, SOR_INT_MASK);
+
 	return 0;
 }
 
@@ -2767,6 +2930,9 @@  static int tegra_sor_exit(struct host1x_client *client)
 	struct tegra_sor *sor = host1x_client_to_sor(client);
 	int err;
 
+	tegra_sor_writel(sor, 0, SOR_INT_MASK);
+	tegra_sor_writel(sor, 0, SOR_INT_ENABLE);
+
 	tegra_output_exit(&sor->output);
 
 	if (sor->aux) {
@@ -3037,6 +3203,54 @@  static int tegra_sor_parse_dt(struct tegra_sor *sor)
 	return 0;
 }
 
+static void tegra_hda_parse_format(unsigned int format, unsigned int *rate,
+				   unsigned int *channels)
+{
+	unsigned int mul, div;
+
+	if (format & AC_FMT_BASE_44K)
+		*rate = 44100;
+	else
+		*rate = 48000;
+
+	mul = (format & AC_FMT_MULT_MASK) >> AC_FMT_MULT_SHIFT;
+	div = (format & AC_FMT_DIV_MASK) >> AC_FMT_DIV_SHIFT;
+
+	*rate = *rate * (mul + 1) / (div + 1);
+
+	*channels = (format & AC_FMT_CHAN_MASK) >> AC_FMT_CHAN_SHIFT;
+}
+
+static irqreturn_t tegra_sor_irq(int irq, void *data)
+{
+	struct tegra_sor *sor = data;
+	u32 value;
+
+	value = tegra_sor_readl(sor, SOR_INT_STATUS);
+	tegra_sor_writel(sor, value, SOR_INT_STATUS);
+
+	if (value & SOR_INT_CODEC_SCRATCH0) {
+		value = tegra_sor_readl(sor, SOR_AUDIO_HDA_CODEC_SCRATCH0);
+
+		if (value & SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID) {
+			unsigned int format, sample_rate, channels;
+
+			format = value & SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK;
+
+			tegra_hda_parse_format(format, &sample_rate, &channels);
+
+			sor->audio.sample_rate = sample_rate;
+			sor->audio.channels = channels;
+
+			tegra_sor_hdmi_audio_enable(sor);
+		} else {
+			tegra_sor_hdmi_audio_disable(sor);
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
 static int tegra_sor_probe(struct platform_device *pdev)
 {
 	struct device_node *np;
@@ -3119,6 +3333,21 @@  static int tegra_sor_probe(struct platform_device *pdev)
 		goto remove;
 	}
 
+	err = platform_get_irq(pdev, 0);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to get IRQ: %d\n", err);
+		goto remove;
+	}
+
+	sor->irq = err;
+
+	err = devm_request_irq(sor->dev, sor->irq, tegra_sor_irq, 0,
+			       dev_name(sor->dev), sor);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to request IRQ: %d\n", err);
+		goto remove;
+	}
+
 	if (!pdev->dev.pm_domain) {
 		sor->rst = devm_reset_control_get(&pdev->dev, "sor");
 		if (IS_ERR(sor->rst)) {
diff --git a/drivers/gpu/drm/tegra/sor.h b/drivers/gpu/drm/tegra/sor.h
index fb0854d92a27..13f7e68bec42 100644
--- a/drivers/gpu/drm/tegra/sor.h
+++ b/drivers/gpu/drm/tegra/sor.h
@@ -364,12 +364,28 @@ 
 #define  INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) << 8)
 #define  INFOFRAME_HEADER_TYPE(x) (((x) & 0xff) << 0)
 
+#define SOR_HDMI_ACR_CTRL 0xb1
+
+#define SOR_HDMI_ACR_0320_SUBPACK_LOW 0xb2
+#define  SOR_HDMI_ACR_SUBPACK_LOW_SB1(x) (((x) & 0xff) << 24)
+
+#define SOR_HDMI_ACR_0320_SUBPACK_HIGH 0xb3
+#define  SOR_HDMI_ACR_SUBPACK_HIGH_ENABLE (1 << 31)
+
+#define SOR_HDMI_ACR_0441_SUBPACK_LOW 0xb4
+#define SOR_HDMI_ACR_0441_SUBPACK_HIGH 0xb5
+
 #define SOR_HDMI_CTRL 0xc0
 #define  SOR_HDMI_CTRL_ENABLE (1 << 30)
 #define  SOR_HDMI_CTRL_MAX_AC_PACKET(x) (((x) & 0x1f) << 16)
 #define  SOR_HDMI_CTRL_AUDIO_LAYOUT (1 << 10)
 #define  SOR_HDMI_CTRL_REKEY(x) (((x) & 0x7f) << 0)
 
+#define SOR_HDMI_SPARE 0xcb
+#define  SOR_HDMI_SPARE_ACR_PRIORITY_HIGH (1 << 31)
+#define  SOR_HDMI_SPARE_CTS_RESET(x) (((x) & 0x7) << 16)
+#define  SOR_HDMI_SPARE_HW_CTS_ENABLE (1 << 0)
+
 #define SOR_REFCLK 0xe6
 #define  SOR_REFCLK_DIV_INT(x) ((((x) >> 2) & 0xff) << 8)
 #define  SOR_REFCLK_DIV_FRAC(x) (((x) & 0x3) << 6)
@@ -378,10 +394,62 @@ 
 #define  SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_LIMITED (1 << 1)
 #define  SOR_INPUT_CONTROL_HDMI_SRC_SELECT(x) (((x) & 0x1) << 0)
 
+#define SOR_AUDIO_CNTRL 0xfc
+#define  SOR_AUDIO_CNTRL_INJECT_NULLSMPL (1 << 29)
+#define  SOR_AUDIO_CNTRL_SOURCE_SELECT(x) (((x) & 0x3) << 20)
+#define   SOURCE_SELECT_MASK 0x3
+#define   SOURCE_SELECT_HDA 0x2
+#define   SOURCE_SELECT_SPDIF 0x1
+#define   SOURCE_SELECT_AUTO 0x0
+#define  SOR_AUDIO_CNTRL_AFIFO_FLUSH (1 << 12)
+
+#define SOR_AUDIO_SPARE 0xfe
+#define  SOR_AUDIO_SPARE_HBR_ENABLE (1 << 27)
+
+#define SOR_AUDIO_NVAL_0320 0xff
+#define SOR_AUDIO_NVAL_0441 0x100
+#define SOR_AUDIO_NVAL_0882 0x101
+#define SOR_AUDIO_NVAL_1764 0x102
+#define SOR_AUDIO_NVAL_0480 0x103
+#define SOR_AUDIO_NVAL_0960 0x104
+#define SOR_AUDIO_NVAL_1920 0x105
+
+#define SOR_AUDIO_HDA_CODEC_SCRATCH0 0x10a
+#define  SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID (1 << 30)
+#define  SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK 0xffff
+
+#define SOR_AUDIO_HDA_ELD_BUFWR 0x10c
+#define  SOR_AUDIO_HDA_ELD_BUFWR_INDEX(x) (((x) & 0xff) << 8)
+#define  SOR_AUDIO_HDA_ELD_BUFWR_DATA(x) (((x) & 0xff) << 0)
+
+#define SOR_AUDIO_HDA_PRESENSE 0x10d
+#define  SOR_AUDIO_HDA_PRESENSE_ELDV (1 << 1)
+#define  SOR_AUDIO_HDA_PRESENSE_PD (1 << 0)
+
+#define SOR_AUDIO_AVAL_0320 0x10f
+#define SOR_AUDIO_AVAL_0441 0x110
+#define SOR_AUDIO_AVAL_0882 0x111
+#define SOR_AUDIO_AVAL_1764 0x112
+#define SOR_AUDIO_AVAL_0480 0x113
+#define SOR_AUDIO_AVAL_0960 0x114
+#define SOR_AUDIO_AVAL_1920 0x115
+
+#define SOR_INT_STATUS 0x11c
+#define  SOR_INT_CODEC_CP_REQUEST (1 << 2)
+#define  SOR_INT_CODEC_SCRATCH1 (1 << 1)
+#define  SOR_INT_CODEC_SCRATCH0 (1 << 0)
+
+#define SOR_INT_MASK 0x11d
+#define SOR_INT_ENABLE 0x11e
+
 #define SOR_HDMI_VSI_INFOFRAME_CTRL 0x123
 #define SOR_HDMI_VSI_INFOFRAME_STATUS 0x124
 #define SOR_HDMI_VSI_INFOFRAME_HEADER 0x125
 
+#define SOR_HDMI_AUDIO_N 0x13c
+#define SOR_HDMI_AUDIO_N_LOOKUP (1 << 28)
+#define SOR_HDMI_AUDIO_N_RESET (1 << 20)
+
 #define SOR_HDMI2_CTRL 0x13e
 #define  SOR_HDMI2_CTRL_CLOCK_MODE_DIV_BY_4 (1 << 1)
 #define  SOR_HDMI2_CTRL_SCRAMBLE (1 << 0)