From 8e2988a76c26f25178b1aa3f345c92dbef40cac4 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Dec 2018 15:46:03 +0100 Subject: drm/tegra: sor: Support for audio over HDMI 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 --- drivers/gpu/drm/tegra/sor.c | 229 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) (limited to 'drivers/gpu/drm/tegra/sor.c') 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 +#include + #include #include #include @@ -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)) { -- cgit v1.2.3 From 016a48b3d66e94c99253b79ca16ac656a9de5740 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 6 Dec 2018 18:57:41 +0100 Subject: drm/tegra: sor: Remove temporary workaround Remove the temporary workaround of storing the Tegra186 HDMI/DP I/O pad ID in the SOR driver. The definition has long been available in the soc/tegra/pmc.h header file. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 8 -------- 1 file changed, 8 deletions(-) (limited to 'drivers/gpu/drm/tegra/sor.c') diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 22a54434a757..07a077bd73e4 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -31,14 +31,6 @@ #include "sor.h" #include "trace.h" -/* - * XXX Remove this after the commit adding it to soc/tegra/pmc.h has been - * merged. Having this around after the commit is merged should be safe since - * the preprocessor will effectively replace all occurrences and therefore no - * duplicate will be defined. - */ -#define TEGRA_IO_PAD_HDMI_DP0 26 - #define SOR_REKEY 0x38 struct tegra_sor_hdmi_settings { -- cgit v1.2.3 From 180b46ecdc9f7762aba65c0b029f59c7a6b314fd Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 6 Dec 2018 18:56:47 +0100 Subject: drm/tegra: sor: Reset the SOR if possible If the SOR is already up and running when the kernel driver is probed, setting a mode will typically fail. This can be seen for example on Jetson TX2. Under certain circumstances the generic power domain code will cause the SOR to be reset. However, if the power domain is never powered off (this can happen if the HDA controller is enabled, which is part of the same power domain as the SOR), then the SOR will end up not getting reset and fail to properly set a mode. To work around this, try to get the reset control and assert/deassert it, irrespective of whether or not a generic power domain is attached to the SOR. On platforms where the kernel implements generic power domains (up to Tegra210) this will fail, because the power domain will already have acquired an exclusive reference to the reset control. But on recent platforms there the BPMP provides an ABI to control power domains, it's possible to acquire the reset control from SOR and use it to put the SOR into a known good state at probe time. The proper solution for this is to make the SOR driver capable of dealing with hardware that's already up and running (by first grace- fully shutting it down, or perhaps by seamlessly transitioning to the kernel driver and taking over the running display configuration). That is fairly involved, though, so we'll go with this quickfix for now. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'drivers/gpu/drm/tegra/sor.c') diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 07a077bd73e4..ef8692b7075a 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -3340,14 +3340,23 @@ static int tegra_sor_probe(struct platform_device *pdev) goto remove; } - if (!pdev->dev.pm_domain) { - sor->rst = devm_reset_control_get(&pdev->dev, "sor"); - if (IS_ERR(sor->rst)) { - err = PTR_ERR(sor->rst); + sor->rst = devm_reset_control_get(&pdev->dev, "sor"); + if (IS_ERR(sor->rst)) { + err = PTR_ERR(sor->rst); + + if (err != -EBUSY || WARN_ON(!pdev->dev.pm_domain)) { dev_err(&pdev->dev, "failed to get reset control: %d\n", err); goto remove; } + + /* + * At this point, the reset control is most likely being used + * by the generic power domain implementation. With any luck + * the power domain will have taken care of resetting the SOR + * and we don't have to do anything. + */ + sor->rst = NULL; } sor->clk = devm_clk_get(&pdev->dev, NULL); -- cgit v1.2.3