All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jani Nikula <jani.nikula@linux.intel.com>
To: Thierry Reding <thierry.reding@gmail.com>Thierry Reding
	<thierry.reding@gmail.com>
Cc: linux-tegra@vger.kernel.org, Sameer Pujar <spujar@nvidia.com>,
	dri-devel@lists.freedesktop.org,
	Jon Hunter <jonathanh@nvidia.com>
Subject: Re: [PATCH] drm/tegra: sor: Support for audio over HDMI
Date: Mon, 03 Dec 2018 18:05:07 +0200	[thread overview]
Message-ID: <875zwa8uks.fsf@intel.com> (raw)
In-Reply-To: <20181203153642.13562-1-thierry.reding@gmail.com>

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)

-- 
Jani Nikula, Intel Open Source Graphics Center
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

  reply	other threads:[~2018-12-03 16:05 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-12-03 15:36 [PATCH] drm/tegra: sor: Support for audio over HDMI Thierry Reding
2018-12-03 16:05 ` Jani Nikula [this message]
2018-12-04  8:42   ` Thierry Reding
2018-12-04  9:33     ` Jani Nikula
2018-12-04 10:09       ` Thierry Reding
2018-12-04 11:09 ` Dmitry Osipenko
2018-12-04 13:08   ` Thierry Reding
2018-12-04 13:50     ` Dmitry Osipenko
2018-12-04 14:40       ` Thierry Reding

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=875zwa8uks.fsf@intel.com \
    --to=jani.nikula@linux.intel.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=jonathanh@nvidia.com \
    --cc=linux-tegra@vger.kernel.org \
    --cc=spujar@nvidia.com \
    --cc=thierry.reding@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.