diff options
author | Thierry Reding <treding@nvidia.com> | 2018-12-03 15:46:03 +0100 |
---|---|---|
committer | Thierry Reding <treding@nvidia.com> | 2018-12-03 16:37:26 +0100 |
commit | 8e2988a76c26f25178b1aa3f345c92dbef40cac4 (patch) | |
tree | a29fa123ce47477b0804258ef0555a5653f3583f /drivers/gpu/drm/tegra | |
parent | e75d04771ad60cb65005f35a7eb65caa6e7c266c (diff) | |
download | linux-8e2988a76c26f25178b1aa3f345c92dbef40cac4.tar.gz linux-8e2988a76c26f25178b1aa3f345c92dbef40cac4.tar.bz2 linux-8e2988a76c26f25178b1aa3f345c92dbef40cac4.zip |
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 <treding@nvidia.com>
Diffstat (limited to 'drivers/gpu/drm/tegra')
-rw-r--r-- | drivers/gpu/drm/tegra/sor.c | 229 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/sor.h | 68 |
2 files changed, 297 insertions, 0 deletions
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) |