Hi everybody,

since i didn't got so much todo in the last week than i thought i would,
i finally sit down and ported the HDMI support for R600 and up chipsets
from radeonhd to KMS.

So the attached patch is based on Dave Airlies drm-next tree
(http://git.kernel.org/?p=linux/kernel/git/airlied/drm-2.6.git;a=shortlog;h=drm-next).

It's been a while since i last coded inside kernel space and the patch
is not very well tested, so there are quite a bunch of "TODO"s inside
the code, but at least it's a starting point and seems to work fine with
the radeon driver+KMS.

So any comments or test results would be very welcome.

Bye,
Christian.
>From d7e609c95102abecb58f2ee5891f439a28293f54 Mon Sep 17 00:00:00 2001
From: Christian Koenig <deathsim...@vodafone.de>
Date: Sun, 27 Sep 2009 20:38:07 +0200
Subject: [PATCH] drm/radeon/kms: HDMI support for R600 KMS

Adding basic HDMI support for R600 KMS, ported from radeonhd ddx.

Signed-off-by: Christian Koenig <deathsim...@vodafone.de>
---
 drivers/gpu/drm/radeon/Makefile          |    2 +-
 drivers/gpu/drm/radeon/r600.c            |    5 +
 drivers/gpu/drm/radeon/r600_audio.c      |  265 ++++++++++++++++
 drivers/gpu/drm/radeon/r600_hdmi.c       |  486 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/radeon/r600_reg.h        |   74 +++++
 drivers/gpu/drm/radeon/radeon.h          |   24 ++
 drivers/gpu/drm/radeon/radeon_drv.c      |    4 +
 drivers/gpu/drm/radeon/radeon_encoders.c |    8 +
 drivers/gpu/drm/radeon/radeon_mode.h     |    3 +
 9 files changed, 870 insertions(+), 1 deletions(-)
 create mode 100644 drivers/gpu/drm/radeon/r600_audio.c
 create mode 100644 drivers/gpu/drm/radeon/r600_hdmi.c

diff --git a/drivers/gpu/drm/radeon/Makefile b/drivers/gpu/drm/radeon/Makefile
index 09a2892..d63e8f7 100644
--- a/drivers/gpu/drm/radeon/Makefile
+++ b/drivers/gpu/drm/radeon/Makefile
@@ -49,7 +49,7 @@ radeon-y += radeon_device.o radeon_kms.o \
 	radeon_cs.o radeon_bios.o radeon_benchmark.o r100.o r300.o r420.o \
 	rs400.o rs600.o rs690.o rv515.o r520.o r600.o rv770.o radeon_test.o \
 	r200.o radeon_legacy_tv.o r600_cs.o r600_blit.o r600_blit_shaders.o \
-	r600_blit_kms.o
+	r600_blit_kms.o r600_audio.o r600_hdmi.o
 
 radeon-$(CONFIG_COMPAT) += radeon_ioc32.o
 
diff --git a/drivers/gpu/drm/radeon/r600.c b/drivers/gpu/drm/radeon/r600.c
index c7233ad..953f7ae 100644
--- a/drivers/gpu/drm/radeon/r600.c
+++ b/drivers/gpu/drm/radeon/r600.c
@@ -1659,6 +1659,10 @@ int r600_init(struct radeon_device *rdev)
 			rdev->accel_working = false;
 		}
 	}
+
+	r = r600_audio_init(rdev);
+	if (r)
+		return r; /* TODO error handling */
 	return 0;
 }
 
@@ -1667,6 +1671,7 @@ void r600_fini(struct radeon_device *rdev)
 	/* Suspend operations */
 	r600_suspend(rdev);
 
+	r600_audio_fini(rdev);
 	r600_blit_fini(rdev);
 	radeon_ring_fini(rdev);
 	r600_pcie_gart_fini(rdev);
diff --git a/drivers/gpu/drm/radeon/r600_audio.c b/drivers/gpu/drm/radeon/r600_audio.c
new file mode 100644
index 0000000..fdcb0f2
--- /dev/null
+++ b/drivers/gpu/drm/radeon/r600_audio.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2008 Advanced Micro Devices, Inc.
+ * Copyright 2008 Red Hat Inc.
+ * Copyright 2009 Christian König.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Christian König
+ */
+#include "drmP.h"
+#include "radeon.h"
+#include "radeon_reg.h"
+#include "atom.h"
+
+#define AUDIO_TIMER_INTERVALL 100 /* 1/10 sekund should be enough */
+
+/*
+ * check if the chipset is supported
+ */
+static int r600_audio_chipset_supported(struct radeon_device *rdev)
+{
+	return rdev->family >= CHIP_R600
+		|| rdev->family == CHIP_RS600
+		|| rdev->family == CHIP_RS690
+		|| rdev->family == CHIP_RS740;
+}
+
+/*
+ * current number of channels
+ */
+static int r600_audio_channels(struct radeon_device *rdev)
+{
+	return (RREG32(R600_AUDIO_RATE_BPS_CHANNEL) & 0x7) + 1;
+}
+
+/*
+ * current bits per sample
+ */
+static int r600_audio_bits_per_sample(struct radeon_device *rdev)
+{
+	uint32_t value = (RREG32(R600_AUDIO_RATE_BPS_CHANNEL) & 0xF0) >> 4;
+	switch(value) {
+	case 0x0: return  8;
+	case 0x1: return 16;
+	case 0x2: return 20;
+	case 0x3: return 24;
+	case 0x4: return 32;
+	}
+
+	DRM_ERROR("Unknown bits per sample 0x%x using 16 instead.\n", (int)value);
+
+	return 16;
+}
+
+/*
+ * current sampling rate in HZ
+ */
+static int r600_audio_rate(struct radeon_device *rdev)
+{
+	uint32_t value = RREG32(R600_AUDIO_RATE_BPS_CHANNEL);
+	uint32_t result;
+
+	if(value & 0x4000)
+		result = 44100;
+	else
+		result = 48000;
+
+	result *= ((value >> 11) & 0x7) + 1;
+	result /= ((value >> 8) & 0x7) + 1;
+
+	return result;
+}
+
+/*
+ * iec 60958 status bits
+ */
+static uint8_t r600_audio_status_bits(struct radeon_device *rdev)
+{
+	return RREG32(R600_AUDIO_STATUS_BITS) & 0xff;
+}
+
+/*
+ * iec 60958 category code
+ */
+static uint8_t r600_audio_category_code(struct radeon_device *rdev)
+{
+	return (RREG32(R600_AUDIO_STATUS_BITS) >> 8) & 0xff;
+}
+
+/*
+ * update all hdmi interfaces with current audio parameters
+ */
+static void r600_audio_update_hdmi(unsigned long param)
+{
+	struct radeon_device *rdev = (struct radeon_device *)param;
+	struct drm_device *dev = rdev->ddev;
+
+	int channels = r600_audio_channels(rdev);
+	int rate = r600_audio_rate(rdev);
+	int bps = r600_audio_bits_per_sample(rdev);
+	uint8_t status_bits = r600_audio_status_bits(rdev);
+	uint8_t category_code = r600_audio_category_code(rdev);
+
+	struct drm_encoder *encoder;
+	int changes = 0;
+
+	changes |= channels != rdev->audio_channels;
+	changes |= rate != rdev->audio_rate;
+	changes |= bps != rdev->audio_bits_per_sample;
+	changes |= status_bits != rdev->audio_status_bits;
+	changes |= category_code != rdev->audio_category_code;
+
+	if(changes) {
+		rdev->audio_channels = channels;
+		rdev->audio_rate = rate;
+		rdev->audio_bits_per_sample = bps;
+		rdev->audio_status_bits = status_bits;
+		rdev->audio_category_code = category_code;
+	}
+
+	list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+		if(changes || r600_hdmi_buffer_status_changed(encoder))
+			r600_hdmi_update_audio_settings(
+				encoder, channels,
+				rate, bps, status_bits,
+				category_code);
+	}
+
+	mod_timer(&rdev->audio_timer,
+		jiffies + msecs_to_jiffies(AUDIO_TIMER_INTERVALL));
+}
+
+/*
+ * initialize the audio vars and register the update timer
+ */
+int r600_audio_init(struct radeon_device *rdev)
+{
+	if(!r600_audio_chipset_supported(rdev))
+		return 0;
+
+	DRM_INFO("%s audio support", radeon_audio ? "Enabling" : "Disabling");
+	WREG32_P(R600_AUDIO_ENABLE, radeon_audio ? 0x81000000 : 0x0, ~0x81000000);
+
+	rdev->audio_channels = -1;
+        rdev->audio_rate = -1;
+        rdev->audio_bits_per_sample = -1;
+        rdev->audio_status_bits = 0;
+        rdev->audio_category_code = 0;
+
+	setup_timer(
+		&rdev->audio_timer,
+		r600_audio_update_hdmi,
+		(unsigned long)rdev);
+
+	mod_timer(&rdev->audio_timer, jiffies + 1);
+
+	return 0;
+}
+
+/*
+ * determin how the encoders and audio interface is wired together
+ */
+int r600_audio_tmds_index(struct drm_encoder *encoder)
+{
+	struct drm_device *dev = encoder->dev;
+        struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder);
+	struct drm_encoder *other;
+
+	switch (radeon_encoder->encoder_id) {
+        case ENCODER_OBJECT_ID_INTERNAL_TMDS1:
+        case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
+	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1:
+		return 0;
+
+        case ENCODER_OBJECT_ID_INTERNAL_LVTM1:
+		/* special case check if an TMDS1 is present */
+		list_for_each_entry(other, &dev->mode_config.encoder_list, head) {
+			if(to_radeon_encoder(other)->encoder_id ==
+				ENCODER_OBJECT_ID_INTERNAL_TMDS1)
+				return 1;
+		}
+		return 0;
+
+        case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
+	case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
+		return 1;
+
+	default:
+		DRM_ERROR("Unsupported encoder type 0x%02X\n", radeon_encoder->encoder_id);
+		return -1;
+	}
+}
+
+/*
+ * atach the audio codec to the clock source of the encoder
+ */
+void r600_audio_set_clock(struct drm_encoder *encoder, int clock)
+{
+        struct drm_device *dev = encoder->dev;
+        struct radeon_device *rdev = dev->dev_private;
+        struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder);
+	int base_rate = 48000;
+
+	switch (radeon_encoder->encoder_id) {
+	case ENCODER_OBJECT_ID_INTERNAL_TMDS1:
+	case ENCODER_OBJECT_ID_INTERNAL_LVTM1:
+		WREG32_P(R600_AUDIO_TIMING, 0, ~0x301);
+		break;
+
+	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
+	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1:
+	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
+	case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
+		WREG32_P(R600_AUDIO_TIMING, 0x100, ~0x301);
+		break;
+
+	default:
+		DRM_ERROR("Unsupported encoder type 0x%02X\n", radeon_encoder->encoder_id);
+		return;
+	}
+
+	switch (r600_audio_tmds_index(encoder)) {
+	case 0:
+		WREG32(R600_AUDIO_PLL1_MUL, base_rate*50);
+		WREG32(R600_AUDIO_PLL1_DIV, clock*100);
+		WREG32(R600_AUDIO_CLK_SRCSEL, 0);
+		break;
+
+	case 1:
+		WREG32(R600_AUDIO_PLL2_MUL, base_rate*50);
+		WREG32(R600_AUDIO_PLL2_DIV, clock*100);
+		WREG32(R600_AUDIO_CLK_SRCSEL, 1);
+		break;
+	}
+}
+
+/*
+ * release the audio timer
+ * TODO: How to do this correctly on SMP systems?
+ */
+void r600_audio_fini(struct radeon_device *rdev)
+{
+	if(!r600_audio_chipset_supported(rdev))
+		return;
+
+	WREG32_P(R600_AUDIO_ENABLE, 0x0, ~0x81000000);
+
+	del_timer(&rdev->audio_timer);
+}
diff --git a/drivers/gpu/drm/radeon/r600_hdmi.c b/drivers/gpu/drm/radeon/r600_hdmi.c
new file mode 100644
index 0000000..f7d42dd
--- /dev/null
+++ b/drivers/gpu/drm/radeon/r600_hdmi.c
@@ -0,0 +1,486 @@
+/*
+ * Copyright 2008 Advanced Micro Devices, Inc.
+ * Copyright 2008 Red Hat Inc.
+ * Copyright 2009 Christian König.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Christian König
+ */
+#include "drmP.h"
+#include "radeon_drm.h"
+#include "radeon.h"
+#include "atom.h"
+
+/*
+ * HDMI color format
+ */
+enum r600_hdmi_color_format {
+	RGB = 0,
+	YCC_422 = 1,
+	YCC_444 = 2
+};
+
+/*
+ * IEC60958 status bits
+ */
+enum r600_hdmi_iec_status_bits {
+        AUDIO_STATUS_DIG_ENABLE   = 0x01,
+        AUDIO_STATUS_V            = 0x02,
+        AUDIO_STATUS_VCFG         = 0x04,
+        AUDIO_STATUS_EMPHASIS     = 0x08,
+        AUDIO_STATUS_COPYRIGHT    = 0x10,
+        AUDIO_STATUS_NONAUDIO     = 0x20,
+        AUDIO_STATUS_PROFESSIONAL = 0x40,
+        AUDIO_STATUS_LEVEL        = 0x80
+};
+
+struct {
+	uint32_t Clock;
+
+	int N_32kHz;
+	int CTS_32kHz;
+
+	int N_44_1kHz;
+	int CTS_44_1kHz;
+
+	int N_48kHz;
+	int CTS_48kHz;
+
+} r600_hdmi_ACR[] = {
+    /*             32kHz          44.1kHz        48kHz    */
+    /* Clock      N     CTS      N     CTS      N     CTS */
+    {  25174,  4576,  28125,  7007,  31250,  6864,  28125 }, /*  25,20/1.001 MHz */
+    {  25200,  4096,  25200,  6272,  28000,  6144,  25200 }, /*  25.20       MHz */
+    {  27000,  4096,  27000,  6272,  30000,  6144,  27000 }, /*  27.00       MHz */
+    {  27027,  4096,  27027,  6272,  30030,  6144,  27027 }, /*  27.00*1.001 MHz */
+    {  54000,  4096,  54000,  6272,  60000,  6144,  54000 }, /*  54.00       MHz */
+    {  54054,  4096,  54054,  6272,  60060,  6144,  54054 }, /*  54.00*1.001 MHz */
+    {  74175, 11648, 210937, 17836, 234375, 11648, 140625 }, /*  74.25/1.001 MHz */
+    {  74250,  4096,  74250,  6272,  82500,  6144,  74250 }, /*  74.25       MHz */
+    { 148351, 11648, 421875,  8918, 234375,  5824, 140625 }, /* 148.50/1.001 MHz */
+    { 148500,  4096, 148500,  6272, 165000,  6144, 148500 }, /* 148.50       MHz */
+    {      0,  4096,      0,  6272,      0,  6144,      0 }  /* Other */
+};
+
+/*
+ * calculate CTS value if it's not found in the table
+ */
+static void r600_hdmi_calc_CTS(uint32_t clock, int* CTS, int N, int freq)
+{
+	if(*CTS == 0) *CTS = clock*N/(128*freq)*1000;
+	DRM_INFO("Using ACR timing N=%d CTS=%d for frequency %d\n",N,*CTS,freq);
+}
+
+/*
+ * update the N and CTS parameters for a given pixel clock rate
+ */
+static void r600_hdmi_update_ACR(struct drm_encoder *encoder, uint32_t clock)
+{
+        struct drm_device *dev = encoder->dev;
+        struct radeon_device *rdev = dev->dev_private;
+	uint32_t offset = to_radeon_encoder(encoder)->hdmi_offset;
+
+	int CTS;
+	int N;
+	int i;
+	for(i=0; r600_hdmi_ACR[i].Clock != clock && r600_hdmi_ACR[i].Clock != 0; i++);
+
+	CTS = r600_hdmi_ACR[i].CTS_32kHz;
+	N = r600_hdmi_ACR[i].N_32kHz;
+	r600_hdmi_calc_CTS(clock, &CTS, N, 32000);
+	WREG32(offset+R600_HDMI_32kHz_CTS, CTS << 12);
+	WREG32(offset+R600_HDMI_32kHz_N, N);
+
+	CTS = r600_hdmi_ACR[i].CTS_44_1kHz;
+	N = r600_hdmi_ACR[i].N_44_1kHz;
+	r600_hdmi_calc_CTS(clock, &CTS, N, 44100);
+	WREG32(offset+R600_HDMI_44_1kHz_CTS, CTS << 12);
+	WREG32(offset+R600_HDMI_44_1kHz_N, N);
+
+	CTS = r600_hdmi_ACR[i].CTS_48kHz;
+	N = r600_hdmi_ACR[i].N_48kHz;
+	r600_hdmi_calc_CTS(clock, &CTS, N, 48000);
+	WREG32(offset+R600_HDMI_48kHz_CTS, CTS << 12);
+	WREG32(offset+R600_HDMI_48kHz_N, N);
+}
+
+/*
+ * calculate the crc for a given info frame
+ */
+static void r600_hdmi_infoframe_checksum(uint8_t packetType, uint8_t versionNumber, uint8_t length, uint8_t* frame)
+{
+    int i;
+    frame[0] = packetType + versionNumber + length;
+    for(i=1;i<=length;i++)
+        frame[0] += frame[i];
+    frame[0] = 0x100 - frame[0];
+}
+
+/*
+ * build a HDMI Video Info Frame
+ */
+static void r600_hdmi_videoinfoframe(
+	struct drm_encoder *encoder,
+	enum r600_hdmi_color_format color_format,
+	int active_information_present,
+	uint8_t active_format_aspect_ratio,
+	uint8_t scan_information,
+	uint8_t colorimetry,
+	uint8_t ex_colorimetry,
+	uint8_t quantization,
+	int ITC,
+	uint8_t picture_aspect_ratio,
+	uint8_t video_format_identification,
+	uint8_t pixel_repetition,
+	uint8_t non_uniform_picture_scaling,
+	uint8_t bar_info_data_valid,
+	uint16_t top_bar,
+	uint16_t bottom_bar,
+	uint16_t left_bar,
+	uint16_t right_bar
+)
+{
+	struct drm_device *dev = encoder->dev;
+	struct radeon_device *rdev = dev->dev_private;
+	uint32_t offset = to_radeon_encoder(encoder)->hdmi_offset;
+
+	uint8_t frame[14];
+
+	frame[0x0] = 0;
+	frame[0x1] =
+		(scan_information & 0x3) |
+		((bar_info_data_valid & 0x3) << 2) |
+		((active_information_present & 0x1) << 4) |
+		((color_format & 0x3) << 5);
+	frame[0x2] =
+		(active_format_aspect_ratio & 0xF) |
+		((picture_aspect_ratio & 0x3) << 4) |
+		((colorimetry & 0x3) << 6);
+	frame[0x3] =
+		(non_uniform_picture_scaling & 0x3) |
+		((quantization & 0x3) << 2) |
+		((ex_colorimetry & 0x7) << 4) |
+		((ITC & 0x1) << 7);
+	frame[0x4] = (video_format_identification & 0x7F);
+	frame[0x5] = (pixel_repetition & 0xF);
+	frame[0x6] = (top_bar & 0xFF);
+	frame[0x7] = (top_bar >> 8);
+	frame[0x8] = (bottom_bar & 0xFF);
+	frame[0x9] = (bottom_bar >> 8);
+	frame[0xA] = (left_bar & 0xFF);
+	frame[0xB] = (left_bar >> 8);
+	frame[0xC] = (right_bar & 0xFF);
+	frame[0xD] = (right_bar >> 8);
+
+	r600_hdmi_infoframe_checksum(0x82, 0x02, 0x0D, frame);
+
+	WREG32(offset+R600_HDMI_VIDEOINFOFRAME_0,
+		frame[0x0] | (frame[0x1] << 8) | (frame[0x2] << 16) | (frame[0x3] << 24));
+	WREG32(offset+R600_HDMI_VIDEOINFOFRAME_1,
+		frame[0x4] | (frame[0x5] << 8) | (frame[0x6] << 16) | (frame[0x7] << 24));
+	WREG32(offset+R600_HDMI_VIDEOINFOFRAME_2,
+		frame[0x8] | (frame[0x9] << 8) | (frame[0xA] << 16) | (frame[0xB] << 24));
+	WREG32(offset+R600_HDMI_VIDEOINFOFRAME_3,
+		frame[0xC] | (frame[0xD] << 8));
+}
+
+/*
+ * build a Audio Info Frame
+ */
+static void r600_hdmi_audioinfoframe(
+	struct drm_encoder *encoder,
+	uint8_t channel_count,
+	uint8_t coding_type,
+	uint8_t sample_size,
+	uint8_t sample_frequency,
+	uint8_t format,
+	uint8_t channel_allocation,
+	uint8_t level_shift,
+	int downmix_inhibit
+)
+{
+	struct drm_device *dev = encoder->dev;
+	struct radeon_device *rdev = dev->dev_private;
+	uint32_t offset = to_radeon_encoder(encoder)->hdmi_offset;
+
+	uint8_t frame[11];
+
+	frame[0x0] = 0;
+	frame[0x1] = (channel_count & 0x7) | ((coding_type & 0xF) << 4);
+	frame[0x2] = (sample_size & 0x3) | ((sample_frequency & 0x7) << 2);
+	frame[0x3] = format;
+	frame[0x4] = channel_allocation;
+	frame[0x5] = ((level_shift & 0xF) << 3) | ((downmix_inhibit & 0x1) << 7);
+	frame[0x6] = 0;
+	frame[0x7] = 0;
+	frame[0x8] = 0;
+	frame[0x9] = 0;
+	frame[0xA] = 0;
+
+	r600_hdmi_infoframe_checksum(0x84, 0x01, 0x0A, frame);
+
+	WREG32(offset+R600_HDMI_AUDIOINFOFRAME_0,
+		frame[0x0] | (frame[0x1] << 8) | (frame[0x2] << 16) | (frame[0x3] << 24));
+	WREG32(offset+R600_HDMI_AUDIOINFOFRAME_1,
+		frame[0x4] | (frame[0x5] << 8) | (frame[0x6] << 16) | (frame[0x8] << 24));
+}
+
+/*
+ * test if audio buffer is filled enough to start playing
+ */
+static int r600_hdmi_is_audio_buffer_filled(struct drm_encoder *encoder)
+{
+	struct drm_device *dev = encoder->dev;
+	struct radeon_device *rdev = dev->dev_private;
+	uint32_t offset = to_radeon_encoder(encoder)->hdmi_offset;
+
+	return (RREG32(offset+R600_HDMI_STATUS) & 0x10) != 0;
+}
+
+/*
+ * have buffer status changed since last call?
+ */
+int r600_hdmi_buffer_status_changed(struct drm_encoder *encoder)
+{
+	struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder);
+	int status, result;
+
+	if(!radeon_encoder->hdmi_offset) return 0;
+
+	status = r600_hdmi_is_audio_buffer_filled(encoder);
+	result = radeon_encoder->hdmi_buffer_status != status;
+	radeon_encoder->hdmi_buffer_status = status;
+
+	return result;
+}
+
+/*
+ * write the audio workaround status to the hardware
+ */
+void r600_hdmi_audio_workaround(struct drm_encoder *encoder)
+{
+	struct drm_device *dev = encoder->dev;
+	struct radeon_device *rdev = dev->dev_private;
+	struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder);
+	uint32_t offset = radeon_encoder->hdmi_offset;
+	if(!offset) return;
+
+	if(r600_hdmi_is_audio_buffer_filled(encoder)) {
+		/* disable audio workaround and start delivering of audio frames */
+		WREG32_P(offset+R600_HDMI_CNTL, 0x00000001, ~0x00001001);
+
+	} else if(radeon_encoder->hdmi_audio_workaround) {
+		/* enable audio workaround and start delivering of audio frames */
+		WREG32_P(offset+R600_HDMI_CNTL, 0x00001001, ~0x00001001);
+
+	} else {
+		/* disable audio workaround and stop delivering of audio frames */
+		WREG32_P(offset+R600_HDMI_CNTL, 0x00000000, ~0x00001001);
+	}
+}
+
+
+/*
+ * update the info frames with the data from the current display mode
+ */
+void r600_hdmi_setmode(struct drm_encoder *encoder, struct drm_display_mode *mode)
+{
+	struct drm_device *dev = encoder->dev;
+	struct radeon_device *rdev = dev->dev_private;
+	uint32_t offset = to_radeon_encoder(encoder)->hdmi_offset;
+	if(!offset) return;
+
+	r600_audio_set_clock(encoder, mode->clock);
+
+	WREG32(offset+R600_HDMI_UNKNOWN_0, 0x1000);
+	WREG32(offset+R600_HDMI_UNKNOWN_1, 0x0);
+	WREG32(offset+R600_HDMI_UNKNOWN_2, 0x1000);
+
+	r600_hdmi_update_ACR(encoder, mode->clock);
+
+	WREG32(offset+R600_HDMI_VIDEOCNTL, 0x13);
+
+	WREG32(offset+R600_HDMI_VERSION, 0x202);
+
+	r600_hdmi_videoinfoframe(encoder, RGB, 0, 0, 0, 0,
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+
+	/* it's unknown what these bits do excatly, but it's indeed quite usefull for debugging */
+	WREG32(offset+R600_HDMI_AUDIO_DEBUG_0, 0x00FFFFFF);
+	WREG32(offset+R600_HDMI_AUDIO_DEBUG_1, 0x007FFFFF);
+	WREG32(offset+R600_HDMI_AUDIO_DEBUG_2, 0x00000001);
+	WREG32(offset+R600_HDMI_AUDIO_DEBUG_3, 0x00000001);
+
+	r600_hdmi_audio_workaround(encoder);
+
+	/* audio packets per line, does anyone know how to calc this ? */
+	WREG32_P(offset+R600_HDMI_CNTL, 0x00040000, ~0x001F0000);
+
+	/* update? reset? don't realy know */
+	WREG32_P(offset+R600_HDMI_CNTL, 0x14000000, ~0x14000000);
+}
+
+/*
+ * update settings with current parameters from audio engine
+ */
+void r600_hdmi_update_audio_settings(
+	struct drm_encoder *encoder,
+	int channels,
+	int rate,
+	int bps,
+	uint8_t status_bits,
+	uint8_t category_code
+)
+{
+	struct drm_device *dev = encoder->dev;
+	struct radeon_device *rdev = dev->dev_private;
+	uint32_t offset = to_radeon_encoder(encoder)->hdmi_offset;
+
+	uint32_t iec;
+	if(!offset) return;
+
+	DRM_INFO("%s with %d channels, %d Hz sampling rate, %d bits per sample,\n",
+		r600_hdmi_is_audio_buffer_filled(encoder) ? "playing" : "stopped",
+		channels, rate, bps);
+	DRM_INFO("0x%02X IEC60958 status bits and 0x%02X category code\n",
+		(int)status_bits, (int)category_code);
+
+	iec = 0;
+	if(status_bits & AUDIO_STATUS_PROFESSIONAL) iec |= 1 << 0;
+	if(status_bits & AUDIO_STATUS_NONAUDIO)     iec |= 1 << 1;
+	if(status_bits & AUDIO_STATUS_COPYRIGHT)    iec |= 1 << 2;
+	if(status_bits & AUDIO_STATUS_EMPHASIS)     iec |= 1 << 3;
+
+	iec |= category_code << 8;
+
+	switch(rate) {
+	case  32000: iec |= 0x3 << 24; break;
+	case  44100: iec |= 0x0 << 24; break;
+	case  88200: iec |= 0x8 << 24; break;
+	case 176400: iec |= 0xc << 24; break;
+	case  48000: iec |= 0x2 << 24; break;
+	case  96000: iec |= 0xa << 24; break;
+	case 192000: iec |= 0xe << 24; break;
+	}
+
+	WREG32(offset+R600_HDMI_IEC60958_1, iec);
+
+	iec = 0;
+	switch(bps) {
+	case 16: iec |= 0x2; break;
+	case 20: iec |= 0x3; break;
+	case 24: iec |= 0xb; break;
+	}
+	if(status_bits & AUDIO_STATUS_V) iec |= 0x5 << 16;
+
+	WREG32_P(offset+R600_HDMI_IEC60958_2, iec, ~0x5000f);
+
+	/* 0x021 or 0x031 sets the audio frame length */
+	WREG32(offset+R600_HDMI_AUDIOCNTL, 0x31);
+	r600_hdmi_audioinfoframe(encoder, channels-1, 0, 0, 0, 0, 0, 0, 0);
+
+	r600_hdmi_audio_workaround(encoder);
+
+	/* update? reset? don't realy know */
+	WREG32_P(offset+R600_HDMI_CNTL, 0x04000000, ~0x04000000);
+}
+
+/*
+ * enable/disable the HDMI engine
+ */
+void r600_hdmi_enable(struct drm_encoder *encoder, int enable)
+{
+	struct drm_device *dev = encoder->dev;
+	struct radeon_device *rdev = dev->dev_private;
+	struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder);
+	uint32_t offset = to_radeon_encoder(encoder)->hdmi_offset;
+	if(!offset) return;
+
+	DRM_INFO("%s HDMI interface @ 0x%04X\n", enable ? "Enabling" : "Disabling", offset);
+
+	/* some version of atombios ignore the enable HDMI flag
+	 * so enabling/disabling HDMI was moved here for TMDS1+2 */
+	switch(radeon_encoder->encoder_id) {
+	case ENCODER_OBJECT_ID_INTERNAL_TMDS1:
+		WREG32_P(AVIVO_TMDSA_CNTL, enable ? 0x4 : 0x0, ~0x4);
+		WREG32(offset+R600_HDMI_ENABLE, enable ? 0x101 : 0x0);
+		break;
+
+        case ENCODER_OBJECT_ID_INTERNAL_LVTM1:
+		WREG32_P(AVIVO_LVTMA_CNTL, enable ? 0x4 : 0x0, ~0x4);
+		WREG32(offset+R600_HDMI_ENABLE, enable ? 0x105 : 0x0);
+		break;
+
+        case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
+        case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1:
+        case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
+        case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
+		/* This part is doubtfull in my opinion */
+		WREG32(offset+R600_HDMI_ENABLE, enable ? 0x110 : 0x0);
+		break;
+
+        default:
+		DRM_ERROR("unknown HDMI output type\n");
+		break;
+	}
+}
+
+/*
+ * determin at which register offset the HDMI encoder is
+ */
+void r600_hdmi_init(struct drm_encoder *encoder)
+{
+	struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder);
+
+	switch(radeon_encoder->encoder_id) {
+	case ENCODER_OBJECT_ID_INTERNAL_TMDS1:
+        case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
+        case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1:
+		radeon_encoder->hdmi_offset = R600_HDMI_TMDS1;
+                break;
+
+        case ENCODER_OBJECT_ID_INTERNAL_LVTM1:
+		switch(r600_audio_tmds_index(encoder)) {
+		case 0:
+			radeon_encoder->hdmi_offset = R600_HDMI_TMDS1;
+			break;
+		case 1:
+			radeon_encoder->hdmi_offset = R600_HDMI_TMDS2;
+			break;
+		default:
+			radeon_encoder->hdmi_offset = 0;
+			break;
+		}
+        case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
+		radeon_encoder->hdmi_offset = R600_HDMI_TMDS2;
+                break;
+
+        case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
+		radeon_encoder->hdmi_offset = R600_HDMI_DIG;
+                break;
+
+	default:
+		radeon_encoder->hdmi_offset = 0;
+                break;
+        }
+
+	/* TODO: make this configureable */
+	radeon_encoder->hdmi_audio_workaround = 0;
+}
diff --git a/drivers/gpu/drm/radeon/r600_reg.h b/drivers/gpu/drm/radeon/r600_reg.h
index e2d1f5f..d0e28ff 100644
--- a/drivers/gpu/drm/radeon/r600_reg.h
+++ b/drivers/gpu/drm/radeon/r600_reg.h
@@ -110,5 +110,79 @@
 #define R600_BIOS_6_SCRATCH               0x173c
 #define R600_BIOS_7_SCRATCH               0x1740
 
+/* Audio, these regs were reverse enginered,
+ * so the chance is high that the naming is wrong
+ * R6xx+ ??? */
+
+/* Audio clocks */
+#define R600_AUDIO_PLL1_MUL               0x0514
+#define R600_AUDIO_PLL1_DIV               0x0518
+#define R600_AUDIO_PLL2_MUL               0x0524
+#define R600_AUDIO_PLL2_DIV               0x0528
+#define R600_AUDIO_CLK_SRCSEL             0x0534
+
+/* Audio general */
+#define R600_AUDIO_ENABLE                 0x7300
+#define R600_AUDIO_TIMING                 0x7344
+
+/* Audio params */
+#define R600_AUDIO_VENDOR_ID              0x7380
+#define R600_AUDIO_REVISION_ID            0x7384
+#define R600_AUDIO_ROOT_NODE_COUNT        0x7388
+#define R600_AUDIO_NID1_NODE_COUNT        0x738c
+#define R600_AUDIO_NID1_TYPE              0x7390
+#define R600_AUDIO_SUPPORTED_SIZE_RATE    0x7394
+#define R600_AUDIO_SUPPORTED_CODEC        0x7398
+#define R600_AUDIO_SUPPORTED_POWER_STATES 0x739c
+#define R600_AUDIO_NID2_CAPS              0x73a0
+#define R600_AUDIO_NID3_CAPS              0x73a4
+#define R600_AUDIO_NID3_PIN_CAPS          0x73a8
+
+/* Audio conn list */
+#define R600_AUDIO_CONN_LIST_LEN          0x73ac
+#define R600_AUDIO_CONN_LIST              0x73b0
+
+/* Audio verbs */
+#define R600_AUDIO_RATE_BPS_CHANNEL       0x73c0
+#define R600_AUDIO_PLAYING                0x73c4
+#define R600_AUDIO_IMPLEMENTATION_ID      0x73c8
+#define R600_AUDIO_CONFIG_DEFAULT         0x73cc
+#define R600_AUDIO_PIN_SENSE              0x73d0
+#define R600_AUDIO_PIN_WIDGET_CNTL        0x73d4
+#define R600_AUDIO_STATUS_BITS            0x73d8
+
+/* HDMI base register addresses */
+#define R600_HDMI_TMDS1                   0x7400
+#define R600_HDMI_TMDS2                   0x7700
+#define R600_HDMI_DIG                     0x7800
+
+/* HDMI registers */
+#define R600_HDMI_ENABLE           0x00
+#define R600_HDMI_STATUS           0x04
+#define R600_HDMI_CNTL             0x08
+#define R600_HDMI_UNKNOWN_0        0x0C
+#define R600_HDMI_AUDIOCNTL        0x10
+#define R600_HDMI_VIDEOCNTL        0x14
+#define R600_HDMI_VERSION          0x18
+#define R600_HDMI_UNKNOWN_1        0x28
+#define R600_HDMI_VIDEOINFOFRAME_0 0x54
+#define R600_HDMI_VIDEOINFOFRAME_1 0x58
+#define R600_HDMI_VIDEOINFOFRAME_2 0x5c
+#define R600_HDMI_VIDEOINFOFRAME_3 0x60
+#define R600_HDMI_32kHz_CTS        0xac
+#define R600_HDMI_32kHz_N          0xb0
+#define R600_HDMI_44_1kHz_CTS      0xb4
+#define R600_HDMI_44_1kHz_N        0xb8
+#define R600_HDMI_48kHz_CTS        0xbc
+#define R600_HDMI_48kHz_N          0xc0
+#define R600_HDMI_AUDIOINFOFRAME_0 0xcc
+#define R600_HDMI_AUDIOINFOFRAME_1 0xd0
+#define R600_HDMI_IEC60958_1       0xd4
+#define R600_HDMI_IEC60958_2       0xd8
+#define R600_HDMI_UNKNOWN_2        0xdc
+#define R600_HDMI_AUDIO_DEBUG_0    0xe0
+#define R600_HDMI_AUDIO_DEBUG_1    0xe4
+#define R600_HDMI_AUDIO_DEBUG_2    0xe8
+#define R600_HDMI_AUDIO_DEBUG_3    0xec
 
 #endif
diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h
index 7e34e43..b05a372 100644
--- a/drivers/gpu/drm/radeon/radeon.h
+++ b/drivers/gpu/drm/radeon/radeon.h
@@ -67,6 +67,7 @@ extern int radeon_benchmarking;
 extern int radeon_testing;
 extern int radeon_connector_table;
 extern int radeon_tv;
+extern int radeon_audio;
 
 /*
  * Copy from radeon_drv.h so we don't have to include both and have conflicting
@@ -776,6 +777,13 @@ struct radeon_device {
 	const struct firmware *me_fw;	/* all family ME firmware */
 	const struct firmware *pfp_fw;	/* r6/700 PFP firmware */
 	struct r600_blit r600_blit;
+	/* audio stuff */
+	struct timer_list	audio_timer;
+	int			audio_channels;
+	int			audio_rate;
+	int			audio_bits_per_sample;
+	uint8_t			audio_status_bits;
+	uint8_t			audio_category_code;
 };
 
 int radeon_device_init(struct radeon_device *rdev,
@@ -1049,5 +1057,21 @@ extern int r600_blit_init(struct radeon_device *rdev);
 extern void r600_blit_fini(struct radeon_device *rdev);
 extern int r600_cp_init_microcode(struct radeon_device *rdev);
 extern int r600_gpu_reset(struct radeon_device *rdev);
+extern int r600_audio_init(struct radeon_device *rdev);
+extern int r600_audio_tmds_index(struct drm_encoder *encoder);
+extern void r600_audio_set_clock(struct drm_encoder *encoder, int clock);
+extern void r600_audio_fini(struct radeon_device *rdev);
+extern void r600_hdmi_init(struct drm_encoder *encoder);
+extern void r600_hdmi_enable(struct drm_encoder *encoder, int enable);
+extern void r600_hdmi_setmode(struct drm_encoder *encoder, struct drm_display_mode *mode);
+extern int r600_hdmi_buffer_status_changed(struct drm_encoder *encoder);
+extern void r600_hdmi_update_audio_settings(
+        struct drm_encoder *encoder,
+        int channels,
+        int rate,
+        int bps,
+        uint8_t status_bits,
+        uint8_t category_code
+);
 
 #endif
diff --git a/drivers/gpu/drm/radeon/radeon_drv.c b/drivers/gpu/drm/radeon/radeon_drv.c
index 7f50fb8..ae0b788 100644
--- a/drivers/gpu/drm/radeon/radeon_drv.c
+++ b/drivers/gpu/drm/radeon/radeon_drv.c
@@ -86,6 +86,7 @@ int radeon_benchmarking = 0;
 int radeon_testing = 0;
 int radeon_connector_table = 0;
 int radeon_tv = 1;
+int radeon_audio = 1;
 
 MODULE_PARM_DESC(no_wb, "Disable AGP writeback for scratch registers");
 module_param_named(no_wb, radeon_no_wb, int, 0444);
@@ -120,6 +121,9 @@ module_param_named(connector_table, radeon_connector_table, int, 0444);
 MODULE_PARM_DESC(tv, "TV enable (0 = disable)");
 module_param_named(tv, radeon_tv, int, 0444);
 
+MODULE_PARM_DESC(audio, "Audio enable (0 = disable)");
+module_param_named(audio, radeon_audio, int, 0444);
+
 static int radeon_suspend(struct drm_device *dev, pm_message_t state)
 {
 	drm_radeon_private_t *dev_priv = dev->dev_private;
diff --git a/drivers/gpu/drm/radeon/radeon_encoders.c b/drivers/gpu/drm/radeon/radeon_encoders.c
index 6216467..b081ccf 100644
--- a/drivers/gpu/drm/radeon/radeon_encoders.c
+++ b/drivers/gpu/drm/radeon/radeon_encoders.c
@@ -1117,6 +1117,9 @@ radeon_atom_encoder_mode_set(struct drm_encoder *encoder,
 	struct radeon_device *rdev = dev->dev_private;
 	struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder);
 	struct radeon_crtc *radeon_crtc = to_radeon_crtc(encoder->crtc);
+        struct drm_connector *connector = radeon_get_connector_for_encoder(encoder);
+
+	int hdmi_detected = drm_detect_hdmi_monitor((struct edid *)connector->edid_blob_ptr);
 
 	if (radeon_encoder->enc_priv) {
 		struct radeon_encoder_atom_dig *dig;
@@ -1173,6 +1176,9 @@ radeon_atom_encoder_mode_set(struct drm_encoder *encoder,
 		break;
 	}
 	atombios_apply_encoder_quirks(encoder, adjusted_mode);
+
+	r600_hdmi_enable(encoder, 1 /* hdmi_detected : autodetection doesn't seems to work */);
+	r600_hdmi_setmode(encoder, adjusted_mode);
 }
 
 static bool
@@ -1417,4 +1423,6 @@ radeon_add_atom_encoder(struct drm_device *dev, uint32_t encoder_id, uint32_t su
 		drm_encoder_helper_add(encoder, &radeon_atom_dig_helper_funcs);
 		break;
 	}
+
+	r600_hdmi_init(encoder);
 }
diff --git a/drivers/gpu/drm/radeon/radeon_mode.h b/drivers/gpu/drm/radeon/radeon_mode.h
index 570a587..838dc61 100644
--- a/drivers/gpu/drm/radeon/radeon_mode.h
+++ b/drivers/gpu/drm/radeon/radeon_mode.h
@@ -296,6 +296,9 @@ struct radeon_encoder {
 	enum radeon_rmx_type rmx_type;
 	struct radeon_native_mode native_mode;
 	void *enc_priv;
+	int hdmi_offset;
+	int hdmi_audio_workaround;
+	int hdmi_buffer_status;
 };
 
 struct radeon_connector_atom_dig {
-- 
1.6.3.3

------------------------------------------------------------------------------
Come build with us! The BlackBerry&reg; Developer Conference in SF, CA
is the only developer event you need to attend this year. Jumpstart your
developing skills, take BlackBerry mobile applications to market and stay 
ahead of the curve. Join us from November 9&#45;12, 2009. Register now&#33;
http://p.sf.net/sfu/devconf
--
_______________________________________________
Dri-devel mailing list
Dri-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/dri-devel

Reply via email to