aboutsummaryrefslogtreecommitdiff
path: root/sys/arm/nvidia/drm2
diff options
context:
space:
mode:
Diffstat (limited to 'sys/arm/nvidia/drm2')
-rw-r--r--sys/arm/nvidia/drm2/hdmi.c1229
-rw-r--r--sys/arm/nvidia/drm2/hdmi.h333
-rw-r--r--sys/arm/nvidia/drm2/tegra_bo.c366
-rw-r--r--sys/arm/nvidia/drm2/tegra_dc.c1441
-rw-r--r--sys/arm/nvidia/drm2/tegra_dc_if.m57
-rw-r--r--sys/arm/nvidia/drm2/tegra_dc_reg.h398
-rw-r--r--sys/arm/nvidia/drm2/tegra_drm.h124
-rw-r--r--sys/arm/nvidia/drm2/tegra_drm_if.m68
-rw-r--r--sys/arm/nvidia/drm2/tegra_drm_subr.c177
-rw-r--r--sys/arm/nvidia/drm2/tegra_fb.c338
-rw-r--r--sys/arm/nvidia/drm2/tegra_hdmi.c1320
-rw-r--r--sys/arm/nvidia/drm2/tegra_hdmi_reg.h283
-rw-r--r--sys/arm/nvidia/drm2/tegra_host1x.c645
13 files changed, 6779 insertions, 0 deletions
diff --git a/sys/arm/nvidia/drm2/hdmi.c b/sys/arm/nvidia/drm2/hdmi.c
new file mode 100644
index 000000000000..1e7ed4a440e8
--- /dev/null
+++ b/sys/arm/nvidia/drm2/hdmi.c
@@ -0,0 +1,1229 @@
+/*
+ * Copyright (C) 2012 Avionic Design GmbH
+ *
+ * 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, sub license,
+ * 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 (including the
+ * next paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+
+#include <arm/nvidia/drm2/hdmi.h>
+
+#define EXPORT_SYMBOL(x)
+#ifndef BIT
+#define BIT(x) (1U << (x))
+#endif
+#define hdmi_log(fmt, ...) printf(fmt, ##__VA_ARGS__)
+
+static uint8_t hdmi_infoframe_checksum(uint8_t *ptr, size_t size)
+{
+ uint8_t csum = 0;
+ size_t i;
+
+ /* compute checksum */
+ for (i = 0; i < size; i++)
+ csum += ptr[i];
+
+ return 256 - csum;
+}
+
+static void hdmi_infoframe_set_checksum(void *buffer, size_t size)
+{
+ uint8_t *ptr = buffer;
+
+ ptr[3] = hdmi_infoframe_checksum(buffer, size);
+}
+
+/**
+ * hdmi_avi_infoframe_init() - initialize an HDMI AVI infoframe
+ * @frame: HDMI AVI infoframe
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+int hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame)
+{
+ memset(frame, 0, sizeof(*frame));
+
+ frame->type = HDMI_INFOFRAME_TYPE_AVI;
+ frame->version = 2;
+ frame->length = HDMI_AVI_INFOFRAME_SIZE;
+
+ return 0;
+}
+EXPORT_SYMBOL(hdmi_avi_infoframe_init);
+
+/**
+ * hdmi_avi_infoframe_pack() - write HDMI AVI infoframe to binary buffer
+ * @frame: HDMI AVI infoframe
+ * @buffer: destination buffer
+ * @size: size of buffer
+ *
+ * Packs the information contained in the @frame structure into a binary
+ * representation that can be written into the corresponding controller
+ * registers. Also computes the checksum as required by section 5.3.5 of
+ * the HDMI 1.4 specification.
+ *
+ * Returns the number of bytes packed into the binary buffer or a negative
+ * error code on failure.
+ */
+ssize_t hdmi_avi_infoframe_pack(struct hdmi_avi_infoframe *frame, void *buffer,
+ size_t size)
+{
+ uint8_t *ptr = buffer;
+ size_t length;
+
+ length = HDMI_INFOFRAME_HEADER_SIZE + frame->length;
+
+ if (size < length)
+ return -ENOSPC;
+
+ memset(buffer, 0, size);
+
+ ptr[0] = frame->type;
+ ptr[1] = frame->version;
+ ptr[2] = frame->length;
+ ptr[3] = 0; /* checksum */
+
+ /* start infoframe payload */
+ ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+ ptr[0] = ((frame->colorspace & 0x3) << 5) | (frame->scan_mode & 0x3);
+
+ /*
+ * Data byte 1, bit 4 has to be set if we provide the active format
+ * aspect ratio
+ */
+ if (frame->active_aspect & 0xf)
+ ptr[0] |= BIT(4);
+
+ /* Bit 3 and 2 indicate if we transmit horizontal/vertical bar data */
+ if (frame->top_bar || frame->bottom_bar)
+ ptr[0] |= BIT(3);
+
+ if (frame->left_bar || frame->right_bar)
+ ptr[0] |= BIT(2);
+
+ ptr[1] = ((frame->colorimetry & 0x3) << 6) |
+ ((frame->picture_aspect & 0x3) << 4) |
+ (frame->active_aspect & 0xf);
+
+ ptr[2] = ((frame->extended_colorimetry & 0x7) << 4) |
+ ((frame->quantization_range & 0x3) << 2) |
+ (frame->nups & 0x3);
+
+ if (frame->itc)
+ ptr[2] |= BIT(7);
+
+ ptr[3] = frame->video_code & 0x7f;
+
+ ptr[4] = ((frame->ycc_quantization_range & 0x3) << 6) |
+ ((frame->content_type & 0x3) << 4) |
+ (frame->pixel_repeat & 0xf);
+
+ ptr[5] = frame->top_bar & 0xff;
+ ptr[6] = (frame->top_bar >> 8) & 0xff;
+ ptr[7] = frame->bottom_bar & 0xff;
+ ptr[8] = (frame->bottom_bar >> 8) & 0xff;
+ ptr[9] = frame->left_bar & 0xff;
+ ptr[10] = (frame->left_bar >> 8) & 0xff;
+ ptr[11] = frame->right_bar & 0xff;
+ ptr[12] = (frame->right_bar >> 8) & 0xff;
+
+ hdmi_infoframe_set_checksum(buffer, length);
+
+ return length;
+}
+EXPORT_SYMBOL(hdmi_avi_infoframe_pack);
+
+/**
+ * hdmi_spd_infoframe_init() - initialize an HDMI SPD infoframe
+ * @frame: HDMI SPD infoframe
+ * @vendor: vendor string
+ * @product: product string
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+int hdmi_spd_infoframe_init(struct hdmi_spd_infoframe *frame,
+ const char *vendor, const char *product)
+{
+ memset(frame, 0, sizeof(*frame));
+
+ frame->type = HDMI_INFOFRAME_TYPE_SPD;
+ frame->version = 1;
+ frame->length = HDMI_SPD_INFOFRAME_SIZE;
+
+ strncpy(frame->vendor, vendor, sizeof(frame->vendor));
+ strncpy(frame->product, product, sizeof(frame->product));
+
+ return 0;
+}
+EXPORT_SYMBOL(hdmi_spd_infoframe_init);
+
+/**
+ * hdmi_spd_infoframe_pack() - write HDMI SPD infoframe to binary buffer
+ * @frame: HDMI SPD infoframe
+ * @buffer: destination buffer
+ * @size: size of buffer
+ *
+ * Packs the information contained in the @frame structure into a binary
+ * representation that can be written into the corresponding controller
+ * registers. Also computes the checksum as required by section 5.3.5 of
+ * the HDMI 1.4 specification.
+ *
+ * Returns the number of bytes packed into the binary buffer or a negative
+ * error code on failure.
+ */
+ssize_t hdmi_spd_infoframe_pack(struct hdmi_spd_infoframe *frame, void *buffer,
+ size_t size)
+{
+ uint8_t *ptr = buffer;
+ size_t length;
+
+ length = HDMI_INFOFRAME_HEADER_SIZE + frame->length;
+
+ if (size < length)
+ return -ENOSPC;
+
+ memset(buffer, 0, size);
+
+ ptr[0] = frame->type;
+ ptr[1] = frame->version;
+ ptr[2] = frame->length;
+ ptr[3] = 0; /* checksum */
+
+ /* start infoframe payload */
+ ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+ memcpy(ptr, frame->vendor, sizeof(frame->vendor));
+ memcpy(ptr + 8, frame->product, sizeof(frame->product));
+
+ ptr[24] = frame->sdi;
+
+ hdmi_infoframe_set_checksum(buffer, length);
+
+ return length;
+}
+EXPORT_SYMBOL(hdmi_spd_infoframe_pack);
+
+/**
+ * hdmi_audio_infoframe_init() - initialize an HDMI audio infoframe
+ * @frame: HDMI audio infoframe
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+int hdmi_audio_infoframe_init(struct hdmi_audio_infoframe *frame)
+{
+ memset(frame, 0, sizeof(*frame));
+
+ frame->type = HDMI_INFOFRAME_TYPE_AUDIO;
+ frame->version = 1;
+ frame->length = HDMI_AUDIO_INFOFRAME_SIZE;
+
+ return 0;
+}
+EXPORT_SYMBOL(hdmi_audio_infoframe_init);
+
+/**
+ * hdmi_audio_infoframe_pack() - write HDMI audio infoframe to binary buffer
+ * @frame: HDMI audio infoframe
+ * @buffer: destination buffer
+ * @size: size of buffer
+ *
+ * Packs the information contained in the @frame structure into a binary
+ * representation that can be written into the corresponding controller
+ * registers. Also computes the checksum as required by section 5.3.5 of
+ * the HDMI 1.4 specification.
+ *
+ * Returns the number of bytes packed into the binary buffer or a negative
+ * error code on failure.
+ */
+ssize_t hdmi_audio_infoframe_pack(struct hdmi_audio_infoframe *frame,
+ void *buffer, size_t size)
+{
+ unsigned char channels;
+ uint8_t *ptr = buffer;
+ size_t length;
+
+ length = HDMI_INFOFRAME_HEADER_SIZE + frame->length;
+
+ if (size < length)
+ return -ENOSPC;
+
+ memset(buffer, 0, size);
+
+ if (frame->channels >= 2)
+ channels = frame->channels - 1;
+ else
+ channels = 0;
+
+ ptr[0] = frame->type;
+ ptr[1] = frame->version;
+ ptr[2] = frame->length;
+ ptr[3] = 0; /* checksum */
+
+ /* start infoframe payload */
+ ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+ ptr[0] = ((frame->coding_type & 0xf) << 4) | (channels & 0x7);
+ ptr[1] = ((frame->sample_frequency & 0x7) << 2) |
+ (frame->sample_size & 0x3);
+ ptr[2] = frame->coding_type_ext & 0x1f;
+ ptr[3] = frame->channel_allocation;
+ ptr[4] = (frame->level_shift_value & 0xf) << 3;
+
+ if (frame->downmix_inhibit)
+ ptr[4] |= BIT(7);
+
+ hdmi_infoframe_set_checksum(buffer, length);
+
+ return length;
+}
+EXPORT_SYMBOL(hdmi_audio_infoframe_pack);
+
+/**
+ * hdmi_vendor_infoframe_init() - initialize an HDMI vendor infoframe
+ * @frame: HDMI vendor infoframe
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+int hdmi_vendor_infoframe_init(struct hdmi_vendor_infoframe *frame)
+{
+ memset(frame, 0, sizeof(*frame));
+
+ frame->type = HDMI_INFOFRAME_TYPE_VENDOR;
+ frame->version = 1;
+
+ frame->oui = HDMI_IEEE_OUI;
+
+ /*
+ * 0 is a valid value for s3d_struct, so we use a special "not set"
+ * value
+ */
+ frame->s3d_struct = HDMI_3D_STRUCTURE_INVALID;
+
+ return 0;
+}
+EXPORT_SYMBOL(hdmi_vendor_infoframe_init);
+
+/**
+ * hdmi_vendor_infoframe_pack() - write a HDMI vendor infoframe to binary buffer
+ * @frame: HDMI infoframe
+ * @buffer: destination buffer
+ * @size: size of buffer
+ *
+ * Packs the information contained in the @frame structure into a binary
+ * representation that can be written into the corresponding controller
+ * registers. Also computes the checksum as required by section 5.3.5 of
+ * the HDMI 1.4 specification.
+ *
+ * Returns the number of bytes packed into the binary buffer or a negative
+ * error code on failure.
+ */
+ssize_t hdmi_vendor_infoframe_pack(struct hdmi_vendor_infoframe *frame,
+ void *buffer, size_t size)
+{
+ uint8_t *ptr = buffer;
+ size_t length;
+
+ /* empty info frame */
+ if (frame->vic == 0 && frame->s3d_struct == HDMI_3D_STRUCTURE_INVALID)
+ return -EINVAL;
+
+ /* only one of those can be supplied */
+ if (frame->vic != 0 && frame->s3d_struct != HDMI_3D_STRUCTURE_INVALID)
+ return -EINVAL;
+
+ /* for side by side (half) we also need to provide 3D_Ext_Data */
+ if (frame->s3d_struct >= HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF)
+ frame->length = 6;
+ else
+ frame->length = 5;
+
+ length = HDMI_INFOFRAME_HEADER_SIZE + frame->length;
+
+ if (size < length)
+ return -ENOSPC;
+
+ memset(buffer, 0, size);
+
+ ptr[0] = frame->type;
+ ptr[1] = frame->version;
+ ptr[2] = frame->length;
+ ptr[3] = 0; /* checksum */
+
+ /* HDMI OUI */
+ ptr[4] = 0x03;
+ ptr[5] = 0x0c;
+ ptr[6] = 0x00;
+
+ if (frame->vic) {
+ ptr[7] = 0x1 << 5; /* video format */
+ ptr[8] = frame->vic;
+ } else {
+ ptr[7] = 0x2 << 5; /* video format */
+ ptr[8] = (frame->s3d_struct & 0xf) << 4;
+ if (frame->s3d_struct >= HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF)
+ ptr[9] = (frame->s3d_ext_data & 0xf) << 4;
+ }
+
+ hdmi_infoframe_set_checksum(buffer, length);
+
+ return length;
+}
+EXPORT_SYMBOL(hdmi_vendor_infoframe_pack);
+
+/*
+ * hdmi_vendor_any_infoframe_pack() - write a vendor infoframe to binary buffer
+ */
+static ssize_t
+hdmi_vendor_any_infoframe_pack(union hdmi_vendor_any_infoframe *frame,
+ void *buffer, size_t size)
+{
+ /* we only know about HDMI vendor infoframes */
+ if (frame->any.oui != HDMI_IEEE_OUI)
+ return -EINVAL;
+
+ return hdmi_vendor_infoframe_pack(&frame->hdmi, buffer, size);
+}
+
+/**
+ * hdmi_infoframe_pack() - write a HDMI infoframe to binary buffer
+ * @frame: HDMI infoframe
+ * @buffer: destination buffer
+ * @size: size of buffer
+ *
+ * Packs the information contained in the @frame structure into a binary
+ * representation that can be written into the corresponding controller
+ * registers. Also computes the checksum as required by section 5.3.5 of
+ * the HDMI 1.4 specification.
+ *
+ * Returns the number of bytes packed into the binary buffer or a negative
+ * error code on failure.
+ */
+ssize_t
+hdmi_infoframe_pack(union hdmi_infoframe *frame, void *buffer, size_t size)
+{
+ ssize_t length;
+
+ switch (frame->any.type) {
+ case HDMI_INFOFRAME_TYPE_AVI:
+ length = hdmi_avi_infoframe_pack(&frame->avi, buffer, size);
+ break;
+ case HDMI_INFOFRAME_TYPE_SPD:
+ length = hdmi_spd_infoframe_pack(&frame->spd, buffer, size);
+ break;
+ case HDMI_INFOFRAME_TYPE_AUDIO:
+ length = hdmi_audio_infoframe_pack(&frame->audio, buffer, size);
+ break;
+ case HDMI_INFOFRAME_TYPE_VENDOR:
+ length = hdmi_vendor_any_infoframe_pack(&frame->vendor,
+ buffer, size);
+ break;
+ default:
+ printf("Bad infoframe type %d\n", frame->any.type);
+ length = -EINVAL;
+ }
+
+ return length;
+}
+EXPORT_SYMBOL(hdmi_infoframe_pack);
+
+static const char *hdmi_infoframe_type_get_name(enum hdmi_infoframe_type type)
+{
+ if (type < 0x80 || type > 0x9f)
+ return "Invalid";
+ switch (type) {
+ case HDMI_INFOFRAME_TYPE_VENDOR:
+ return "Vendor";
+ case HDMI_INFOFRAME_TYPE_AVI:
+ return "Auxiliary Video Information (AVI)";
+ case HDMI_INFOFRAME_TYPE_SPD:
+ return "Source Product Description (SPD)";
+ case HDMI_INFOFRAME_TYPE_AUDIO:
+ return "Audio";
+ }
+ return "Reserved";
+}
+
+static void hdmi_infoframe_log_header(struct hdmi_any_infoframe *frame)
+{
+ hdmi_log("HDMI infoframe: %s, version %u, length %u\n",
+ hdmi_infoframe_type_get_name(frame->type),
+ frame->version, frame->length);
+}
+
+static const char *hdmi_colorspace_get_name(enum hdmi_colorspace colorspace)
+{
+ switch (colorspace) {
+ case HDMI_COLORSPACE_RGB:
+ return "RGB";
+ case HDMI_COLORSPACE_YUV422:
+ return "YCbCr 4:2:2";
+ case HDMI_COLORSPACE_YUV444:
+ return "YCbCr 4:4:4";
+ case HDMI_COLORSPACE_YUV420:
+ return "YCbCr 4:2:0";
+ case HDMI_COLORSPACE_RESERVED4:
+ return "Reserved (4)";
+ case HDMI_COLORSPACE_RESERVED5:
+ return "Reserved (5)";
+ case HDMI_COLORSPACE_RESERVED6:
+ return "Reserved (6)";
+ case HDMI_COLORSPACE_IDO_DEFINED:
+ return "IDO Defined";
+ }
+ return "Invalid";
+}
+
+static const char *hdmi_scan_mode_get_name(enum hdmi_scan_mode scan_mode)
+{
+ switch (scan_mode) {
+ case HDMI_SCAN_MODE_NONE:
+ return "No Data";
+ case HDMI_SCAN_MODE_OVERSCAN:
+ return "Overscan";
+ case HDMI_SCAN_MODE_UNDERSCAN:
+ return "Underscan";
+ case HDMI_SCAN_MODE_RESERVED:
+ return "Reserved";
+ }
+ return "Invalid";
+}
+
+static const char *hdmi_colorimetry_get_name(enum hdmi_colorimetry colorimetry)
+{
+ switch (colorimetry) {
+ case HDMI_COLORIMETRY_NONE:
+ return "No Data";
+ case HDMI_COLORIMETRY_ITU_601:
+ return "ITU601";
+ case HDMI_COLORIMETRY_ITU_709:
+ return "ITU709";
+ case HDMI_COLORIMETRY_EXTENDED:
+ return "Extended";
+ }
+ return "Invalid";
+}
+
+static const char *
+hdmi_picture_aspect_get_name(enum hdmi_picture_aspect picture_aspect)
+{
+ switch (picture_aspect) {
+ case HDMI_PICTURE_ASPECT_NONE:
+ return "No Data";
+ case HDMI_PICTURE_ASPECT_4_3:
+ return "4:3";
+ case HDMI_PICTURE_ASPECT_16_9:
+ return "16:9";
+ case HDMI_PICTURE_ASPECT_RESERVED:
+ return "Reserved";
+ }
+ return "Invalid";
+}
+
+static const char *
+hdmi_active_aspect_get_name(enum hdmi_active_aspect active_aspect)
+{
+ if (active_aspect > 0xf)
+ return "Invalid";
+
+ switch (active_aspect) {
+ case HDMI_ACTIVE_ASPECT_16_9_TOP:
+ return "16:9 Top";
+ case HDMI_ACTIVE_ASPECT_14_9_TOP:
+ return "14:9 Top";
+ case HDMI_ACTIVE_ASPECT_16_9_CENTER:
+ return "16:9 Center";
+ case HDMI_ACTIVE_ASPECT_PICTURE:
+ return "Same as Picture";
+ case HDMI_ACTIVE_ASPECT_4_3:
+ return "4:3";
+ case HDMI_ACTIVE_ASPECT_16_9:
+ return "16:9";
+ case HDMI_ACTIVE_ASPECT_14_9:
+ return "14:9";
+ case HDMI_ACTIVE_ASPECT_4_3_SP_14_9:
+ return "4:3 SP 14:9";
+ case HDMI_ACTIVE_ASPECT_16_9_SP_14_9:
+ return "16:9 SP 14:9";
+ case HDMI_ACTIVE_ASPECT_16_9_SP_4_3:
+ return "16:9 SP 4:3";
+ }
+ return "Reserved";
+}
+
+static const char *
+hdmi_extended_colorimetry_get_name(enum hdmi_extended_colorimetry ext_col)
+{
+ switch (ext_col) {
+ case HDMI_EXTENDED_COLORIMETRY_XV_YCC_601:
+ return "xvYCC 601";
+ case HDMI_EXTENDED_COLORIMETRY_XV_YCC_709:
+ return "xvYCC 709";
+ case HDMI_EXTENDED_COLORIMETRY_S_YCC_601:
+ return "sYCC 601";
+ case HDMI_EXTENDED_COLORIMETRY_ADOBE_YCC_601:
+ return "Adobe YCC 601";
+ case HDMI_EXTENDED_COLORIMETRY_ADOBE_RGB:
+ return "Adobe RGB";
+ case HDMI_EXTENDED_COLORIMETRY_BT2020_CONST_LUM:
+ return "BT.2020 Constant Luminance";
+ case HDMI_EXTENDED_COLORIMETRY_BT2020:
+ return "BT.2020";
+ case HDMI_EXTENDED_COLORIMETRY_RESERVED:
+ return "Reserved";
+ }
+ return "Invalid";
+}
+
+static const char *
+hdmi_quantization_range_get_name(enum hdmi_quantization_range qrange)
+{
+ switch (qrange) {
+ case HDMI_QUANTIZATION_RANGE_DEFAULT:
+ return "Default";
+ case HDMI_QUANTIZATION_RANGE_LIMITED:
+ return "Limited";
+ case HDMI_QUANTIZATION_RANGE_FULL:
+ return "Full";
+ case HDMI_QUANTIZATION_RANGE_RESERVED:
+ return "Reserved";
+ }
+ return "Invalid";
+}
+
+static const char *hdmi_nups_get_name(enum hdmi_nups nups)
+{
+ switch (nups) {
+ case HDMI_NUPS_UNKNOWN:
+ return "Unknown Non-uniform Scaling";
+ case HDMI_NUPS_HORIZONTAL:
+ return "Horizontally Scaled";
+ case HDMI_NUPS_VERTICAL:
+ return "Vertically Scaled";
+ case HDMI_NUPS_BOTH:
+ return "Horizontally and Vertically Scaled";
+ }
+ return "Invalid";
+}
+
+static const char *
+hdmi_ycc_quantization_range_get_name(enum hdmi_ycc_quantization_range qrange)
+{
+ switch (qrange) {
+ case HDMI_YCC_QUANTIZATION_RANGE_LIMITED:
+ return "Limited";
+ case HDMI_YCC_QUANTIZATION_RANGE_FULL:
+ return "Full";
+ }
+ return "Invalid";
+}
+
+static const char *
+hdmi_content_type_get_name(enum hdmi_content_type content_type)
+{
+ switch (content_type) {
+ case HDMI_CONTENT_TYPE_GRAPHICS:
+ return "Graphics";
+ case HDMI_CONTENT_TYPE_PHOTO:
+ return "Photo";
+ case HDMI_CONTENT_TYPE_CINEMA:
+ return "Cinema";
+ case HDMI_CONTENT_TYPE_GAME:
+ return "Game";
+ }
+ return "Invalid";
+}
+
+/**
+ * hdmi_avi_infoframe_log() - log info of HDMI AVI infoframe
+ * @level: logging level
+ * @dev: device
+ * @frame: HDMI AVI infoframe
+ */
+static void hdmi_avi_infoframe_log(struct hdmi_avi_infoframe *frame)
+{
+ hdmi_infoframe_log_header((struct hdmi_any_infoframe *)frame);
+
+ hdmi_log(" colorspace: %s\n",
+ hdmi_colorspace_get_name(frame->colorspace));
+ hdmi_log(" scan mode: %s\n",
+ hdmi_scan_mode_get_name(frame->scan_mode));
+ hdmi_log(" colorimetry: %s\n",
+ hdmi_colorimetry_get_name(frame->colorimetry));
+ hdmi_log(" picture aspect: %s\n",
+ hdmi_picture_aspect_get_name(frame->picture_aspect));
+ hdmi_log(" active aspect: %s\n",
+ hdmi_active_aspect_get_name(frame->active_aspect));
+ hdmi_log(" itc: %s\n", frame->itc ? "IT Content" : "No Data");
+ hdmi_log(" extended colorimetry: %s\n",
+ hdmi_extended_colorimetry_get_name(frame->extended_colorimetry));
+ hdmi_log(" quantization range: %s\n",
+ hdmi_quantization_range_get_name(frame->quantization_range));
+ hdmi_log(" nups: %s\n", hdmi_nups_get_name(frame->nups));
+ hdmi_log(" video code: %u\n", frame->video_code);
+ hdmi_log(" ycc quantization range: %s\n",
+ hdmi_ycc_quantization_range_get_name(frame->ycc_quantization_range));
+ hdmi_log(" hdmi content type: %s\n",
+ hdmi_content_type_get_name(frame->content_type));
+ hdmi_log(" pixel repeat: %u\n", frame->pixel_repeat);
+ hdmi_log(" bar top %u, bottom %u, left %u, right %u\n",
+ frame->top_bar, frame->bottom_bar,
+ frame->left_bar, frame->right_bar);
+}
+
+static const char *hdmi_spd_sdi_get_name(enum hdmi_spd_sdi sdi)
+{
+;
+ switch (sdi) {
+ case HDMI_SPD_SDI_UNKNOWN:
+ return "Unknown";
+ case HDMI_SPD_SDI_DSTB:
+ return "Digital STB";
+ case HDMI_SPD_SDI_DVDP:
+ return "DVD Player";
+ case HDMI_SPD_SDI_DVHS:
+ return "D-VHS";
+ case HDMI_SPD_SDI_HDDVR:
+ return "HDD Videorecorder";
+ case HDMI_SPD_SDI_DVC:
+ return "DVC";
+ case HDMI_SPD_SDI_DSC:
+ return "DSC";
+ case HDMI_SPD_SDI_VCD:
+ return "Video CD";
+ case HDMI_SPD_SDI_GAME:
+ return "Game";
+ case HDMI_SPD_SDI_PC:
+ return "PC General";
+ case HDMI_SPD_SDI_BD:
+ return "Blu-Ray Disc (BD)";
+ case HDMI_SPD_SDI_SACD:
+ return "Super Audio CD";
+ case HDMI_SPD_SDI_HDDVD:
+ return "HD DVD";
+ case HDMI_SPD_SDI_PMP:
+ return "PMP";
+ }
+ return "Reserved";
+}
+
+/**
+ * hdmi_spd_infoframe_log() - log info of HDMI SPD infoframe
+ * @level: logging level
+ * @dev: device
+ * @frame: HDMI SPD infoframe
+ */
+static void hdmi_spd_infoframe_log(struct hdmi_spd_infoframe *frame)
+{
+ uint8_t buf[17];
+
+ hdmi_infoframe_log_header((struct hdmi_any_infoframe *)frame);
+
+ memset(buf, 0, sizeof(buf));
+
+ strncpy(buf, frame->vendor, 8);
+ hdmi_log(" vendor: %s\n", buf);
+ strncpy(buf, frame->product, 16);
+ hdmi_log(" product: %s\n", buf);
+ hdmi_log(" source device information: %s (0x%x)\n",
+ hdmi_spd_sdi_get_name(frame->sdi), frame->sdi);
+}
+
+static const char *
+hdmi_audio_coding_type_get_name(enum hdmi_audio_coding_type coding_type)
+{
+ switch (coding_type) {
+ case HDMI_AUDIO_CODING_TYPE_STREAM:
+ return "Refer to Stream Header";
+ case HDMI_AUDIO_CODING_TYPE_PCM:
+ return "PCM";
+ case HDMI_AUDIO_CODING_TYPE_AC3:
+ return "AC-3";
+ case HDMI_AUDIO_CODING_TYPE_MPEG1:
+ return "MPEG1";
+ case HDMI_AUDIO_CODING_TYPE_MP3:
+ return "MP3";
+ case HDMI_AUDIO_CODING_TYPE_MPEG2:
+ return "MPEG2";
+ case HDMI_AUDIO_CODING_TYPE_AAC_LC:
+ return "AAC";
+ case HDMI_AUDIO_CODING_TYPE_DTS:
+ return "DTS";
+ case HDMI_AUDIO_CODING_TYPE_ATRAC:
+ return "ATRAC";
+ case HDMI_AUDIO_CODING_TYPE_DSD:
+ return "One Bit Audio";
+ case HDMI_AUDIO_CODING_TYPE_EAC3:
+ return "Dolby Digital +";
+ case HDMI_AUDIO_CODING_TYPE_DTS_HD:
+ return "DTS-HD";
+ case HDMI_AUDIO_CODING_TYPE_MLP:
+ return "MAT (MLP)";
+ case HDMI_AUDIO_CODING_TYPE_DST:
+ return "DST";
+ case HDMI_AUDIO_CODING_TYPE_WMA_PRO:
+ return "WMA PRO";
+ case HDMI_AUDIO_CODING_TYPE_CXT:
+ return "Refer to CXT";
+ }
+ return "Invalid";
+}
+
+static const char *
+hdmi_audio_sample_size_get_name(enum hdmi_audio_sample_size sample_size)
+{
+ switch (sample_size) {
+ case HDMI_AUDIO_SAMPLE_SIZE_STREAM:
+ return "Refer to Stream Header";
+ case HDMI_AUDIO_SAMPLE_SIZE_16:
+ return "16 bit";
+ case HDMI_AUDIO_SAMPLE_SIZE_20:
+ return "20 bit";
+ case HDMI_AUDIO_SAMPLE_SIZE_24:
+ return "24 bit";
+ }
+ return "Invalid";
+}
+
+static const char *
+hdmi_audio_sample_frequency_get_name(enum hdmi_audio_sample_frequency freq)
+{
+ switch (freq) {
+ case HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM:
+ return "Refer to Stream Header";
+ case HDMI_AUDIO_SAMPLE_FREQUENCY_32000:
+ return "32 kHz";
+ case HDMI_AUDIO_SAMPLE_FREQUENCY_44100:
+ return "44.1 kHz (CD)";
+ case HDMI_AUDIO_SAMPLE_FREQUENCY_48000:
+ return "48 kHz";
+ case HDMI_AUDIO_SAMPLE_FREQUENCY_88200:
+ return "88.2 kHz";
+ case HDMI_AUDIO_SAMPLE_FREQUENCY_96000:
+ return "96 kHz";
+ case HDMI_AUDIO_SAMPLE_FREQUENCY_176400:
+ return "176.4 kHz";
+ case HDMI_AUDIO_SAMPLE_FREQUENCY_192000:
+ return "192 kHz";
+ }
+ return "Invalid";
+}
+
+static const char *
+hdmi_audio_coding_type_ext_get_name(enum hdmi_audio_coding_type_ext ctx)
+{
+
+ switch (ctx) {
+ case HDMI_AUDIO_CODING_TYPE_EXT_CT:
+ return "Refer to CT";
+ case HDMI_AUDIO_CODING_TYPE_EXT_HE_AAC:
+ return "HE AAC";
+ case HDMI_AUDIO_CODING_TYPE_EXT_HE_AAC_V2:
+ return "HE AAC v2";
+ case HDMI_AUDIO_CODING_TYPE_EXT_MPEG_SURROUND:
+ return "MPEG SURROUND";
+ case HDMI_AUDIO_CODING_TYPE_EXT_MPEG4_HE_AAC:
+ return "MPEG-4 HE AAC";
+ case HDMI_AUDIO_CODING_TYPE_EXT_MPEG4_HE_AAC_V2:
+ return "MPEG-4 HE AAC v2";
+ case HDMI_AUDIO_CODING_TYPE_EXT_MPEG4_AAC_LC:
+ return "MPEG-4 AAC LC";
+ case HDMI_AUDIO_CODING_TYPE_EXT_DRA:
+ return "DRA";
+ case HDMI_AUDIO_CODING_TYPE_EXT_MPEG4_HE_AAC_SURROUND:
+ return "MPEG-4 HE AAC + MPEG Surround";
+ case HDMI_AUDIO_CODING_TYPE_EXT_MPEG4_AAC_LC_SURROUND:
+ return "MPEG-4 AAC LC + MPEG Surround";
+ }
+ return "Reserved";
+}
+
+/**
+ * hdmi_audio_infoframe_log() - log info of HDMI AUDIO infoframe
+ * @level: logging level
+ * @dev: device
+ * @frame: HDMI AUDIO infoframe
+ */
+static void hdmi_audio_infoframe_log(struct hdmi_audio_infoframe *frame)
+{
+ hdmi_infoframe_log_header((struct hdmi_any_infoframe *)frame);
+
+ if (frame->channels)
+ hdmi_log(" channels: %u\n", frame->channels - 1);
+ else
+ hdmi_log(" channels: Refer to stream header\n");
+ hdmi_log(" coding type: %s\n",
+ hdmi_audio_coding_type_get_name(frame->coding_type));
+ hdmi_log(" sample size: %s\n",
+ hdmi_audio_sample_size_get_name(frame->sample_size));
+ hdmi_log(" sample frequency: %s\n",
+ hdmi_audio_sample_frequency_get_name(frame->sample_frequency));
+ hdmi_log(" coding type ext: %s\n",
+ hdmi_audio_coding_type_ext_get_name(frame->coding_type_ext));
+ hdmi_log(" channel allocation: 0x%x\n",
+ frame->channel_allocation);
+ hdmi_log(" level shift value: %u dB\n",
+ frame->level_shift_value);
+ hdmi_log(" downmix inhibit: %s\n",
+ frame->downmix_inhibit ? "Yes" : "No");
+}
+
+static const char *
+hdmi_3d_structure_get_name(enum hdmi_3d_structure s3d_struct)
+{
+ if (s3d_struct < 0 || s3d_struct > 0xf)
+ return "Invalid";
+
+ switch (s3d_struct) {
+ case HDMI_3D_STRUCTURE_FRAME_PACKING:
+ return "Frame Packing";
+ case HDMI_3D_STRUCTURE_FIELD_ALTERNATIVE:
+ return "Field Alternative";
+ case HDMI_3D_STRUCTURE_LINE_ALTERNATIVE:
+ return "Line Alternative";
+ case HDMI_3D_STRUCTURE_SIDE_BY_SIDE_FULL:
+ return "Side-by-side (Full)";
+ case HDMI_3D_STRUCTURE_L_DEPTH:
+ return "L + Depth";
+ case HDMI_3D_STRUCTURE_L_DEPTH_GFX_GFX_DEPTH:
+ return "L + Depth + Graphics + Graphics-depth";
+ case HDMI_3D_STRUCTURE_TOP_AND_BOTTOM:
+ return "Top-and-Bottom";
+ case HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF:
+ return "Side-by-side (Half)";
+ default:
+ break;
+ }
+ return "Reserved";
+}
+
+/**
+ * hdmi_vendor_infoframe_log() - log info of HDMI VENDOR infoframe
+ * @level: logging level
+ * @dev: device
+ * @frame: HDMI VENDOR infoframe
+ */
+static void
+hdmi_vendor_any_infoframe_log(union hdmi_vendor_any_infoframe *frame)
+{
+ struct hdmi_vendor_infoframe *hvf = &frame->hdmi;
+
+ hdmi_infoframe_log_header((struct hdmi_any_infoframe *)frame);
+
+ if (frame->any.oui != HDMI_IEEE_OUI) {
+ hdmi_log(" not a HDMI vendor infoframe\n");
+ return;
+ }
+ if (hvf->vic == 0 && hvf->s3d_struct == HDMI_3D_STRUCTURE_INVALID) {
+ hdmi_log(" empty frame\n");
+ return;
+ }
+
+ if (hvf->vic)
+ hdmi_log(" HDMI VIC: %u\n", hvf->vic);
+ if (hvf->s3d_struct != HDMI_3D_STRUCTURE_INVALID) {
+ hdmi_log(" 3D structure: %s\n",
+ hdmi_3d_structure_get_name(hvf->s3d_struct));
+ if (hvf->s3d_struct >= HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF)
+ hdmi_log(" 3D extension data: %d\n",
+ hvf->s3d_ext_data);
+ }
+}
+
+/**
+ * hdmi_infoframe_log() - log info of HDMI infoframe
+ * @level: logging level
+ * @dev: device
+ * @frame: HDMI infoframe
+ */
+void hdmi_infoframe_log(union hdmi_infoframe *frame)
+{
+ switch (frame->any.type) {
+ case HDMI_INFOFRAME_TYPE_AVI:
+ hdmi_avi_infoframe_log(&frame->avi);
+ break;
+ case HDMI_INFOFRAME_TYPE_SPD:
+ hdmi_spd_infoframe_log(&frame->spd);
+ break;
+ case HDMI_INFOFRAME_TYPE_AUDIO:
+ hdmi_audio_infoframe_log(&frame->audio);
+ break;
+ case HDMI_INFOFRAME_TYPE_VENDOR:
+ hdmi_vendor_any_infoframe_log(&frame->vendor);
+ break;
+ }
+}
+EXPORT_SYMBOL(hdmi_infoframe_log);
+
+/**
+ * hdmi_avi_infoframe_unpack() - unpack binary buffer to a HDMI AVI infoframe
+ * @buffer: source buffer
+ * @frame: HDMI AVI infoframe
+ *
+ * Unpacks the information contained in binary @buffer into a structured
+ * @frame of the HDMI Auxiliary Video (AVI) information frame.
+ * Also verifies the checksum as required by section 5.3.5 of the HDMI 1.4
+ * specification.
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+static int hdmi_avi_infoframe_unpack(struct hdmi_avi_infoframe *frame,
+ void *buffer)
+{
+ uint8_t *ptr = buffer;
+ int ret;
+
+ if (ptr[0] != HDMI_INFOFRAME_TYPE_AVI ||
+ ptr[1] != 2 ||
+ ptr[2] != HDMI_AVI_INFOFRAME_SIZE)
+ return -EINVAL;
+
+ if (hdmi_infoframe_checksum(buffer, HDMI_INFOFRAME_SIZE(AVI)) != 0)
+ return -EINVAL;
+
+ ret = hdmi_avi_infoframe_init(frame);
+ if (ret)
+ return ret;
+
+ ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+ frame->colorspace = (ptr[0] >> 5) & 0x3;
+ if (ptr[0] & 0x10)
+ frame->active_aspect = ptr[1] & 0xf;
+ if (ptr[0] & 0x8) {
+ frame->top_bar = (ptr[5] << 8) + ptr[6];
+ frame->bottom_bar = (ptr[7] << 8) + ptr[8];
+ }
+ if (ptr[0] & 0x4) {
+ frame->left_bar = (ptr[9] << 8) + ptr[10];
+ frame->right_bar = (ptr[11] << 8) + ptr[12];
+ }
+ frame->scan_mode = ptr[0] & 0x3;
+
+ frame->colorimetry = (ptr[1] >> 6) & 0x3;
+ frame->picture_aspect = (ptr[1] >> 4) & 0x3;
+ frame->active_aspect = ptr[1] & 0xf;
+
+ frame->itc = ptr[2] & 0x80 ? true : false;
+ frame->extended_colorimetry = (ptr[2] >> 4) & 0x7;
+ frame->quantization_range = (ptr[2] >> 2) & 0x3;
+ frame->nups = ptr[2] & 0x3;
+
+ frame->video_code = ptr[3] & 0x7f;
+ frame->ycc_quantization_range = (ptr[4] >> 6) & 0x3;
+ frame->content_type = (ptr[4] >> 4) & 0x3;
+
+ frame->pixel_repeat = ptr[4] & 0xf;
+
+ return 0;
+}
+
+/**
+ * hdmi_spd_infoframe_unpack() - unpack binary buffer to a HDMI SPD infoframe
+ * @buffer: source buffer
+ * @frame: HDMI SPD infoframe
+ *
+ * Unpacks the information contained in binary @buffer into a structured
+ * @frame of the HDMI Source Product Description (SPD) information frame.
+ * Also verifies the checksum as required by section 5.3.5 of the HDMI 1.4
+ * specification.
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+static int hdmi_spd_infoframe_unpack(struct hdmi_spd_infoframe *frame,
+ void *buffer)
+{
+ uint8_t *ptr = buffer;
+ int ret;
+
+ if (ptr[0] != HDMI_INFOFRAME_TYPE_SPD ||
+ ptr[1] != 1 ||
+ ptr[2] != HDMI_SPD_INFOFRAME_SIZE) {
+ return -EINVAL;
+ }
+
+ if (hdmi_infoframe_checksum(buffer, HDMI_INFOFRAME_SIZE(SPD)) != 0)
+ return -EINVAL;
+
+ ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+ ret = hdmi_spd_infoframe_init(frame, ptr, ptr + 8);
+ if (ret)
+ return ret;
+
+ frame->sdi = ptr[24];
+
+ return 0;
+}
+
+/**
+ * hdmi_audio_infoframe_unpack() - unpack binary buffer to a HDMI AUDIO infoframe
+ * @buffer: source buffer
+ * @frame: HDMI Audio infoframe
+ *
+ * Unpacks the information contained in binary @buffer into a structured
+ * @frame of the HDMI Audio information frame.
+ * Also verifies the checksum as required by section 5.3.5 of the HDMI 1.4
+ * specification.
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+static int hdmi_audio_infoframe_unpack(struct hdmi_audio_infoframe *frame,
+ void *buffer)
+{
+ uint8_t *ptr = buffer;
+ int ret;
+
+ if (ptr[0] != HDMI_INFOFRAME_TYPE_AUDIO ||
+ ptr[1] != 1 ||
+ ptr[2] != HDMI_AUDIO_INFOFRAME_SIZE) {
+ return -EINVAL;
+ }
+
+ if (hdmi_infoframe_checksum(buffer, HDMI_INFOFRAME_SIZE(AUDIO)) != 0)
+ return -EINVAL;
+
+ ret = hdmi_audio_infoframe_init(frame);
+ if (ret)
+ return ret;
+
+ ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+ frame->channels = ptr[0] & 0x7;
+ frame->coding_type = (ptr[0] >> 4) & 0xf;
+ frame->sample_size = ptr[1] & 0x3;
+ frame->sample_frequency = (ptr[1] >> 2) & 0x7;
+ frame->coding_type_ext = ptr[2] & 0x1f;
+ frame->channel_allocation = ptr[3];
+ frame->level_shift_value = (ptr[4] >> 3) & 0xf;
+ frame->downmix_inhibit = ptr[4] & 0x80 ? true : false;
+
+ return 0;
+}
+
+/**
+ * hdmi_vendor_infoframe_unpack() - unpack binary buffer to a HDMI vendor infoframe
+ * @buffer: source buffer
+ * @frame: HDMI Vendor infoframe
+ *
+ * Unpacks the information contained in binary @buffer into a structured
+ * @frame of the HDMI Vendor information frame.
+ * Also verifies the checksum as required by section 5.3.5 of the HDMI 1.4
+ * specification.
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+static int
+hdmi_vendor_any_infoframe_unpack(union hdmi_vendor_any_infoframe *frame,
+ void *buffer)
+{
+ uint8_t *ptr = buffer;
+ size_t length;
+ int ret;
+ uint8_t hdmi_video_format;
+ struct hdmi_vendor_infoframe *hvf = &frame->hdmi;
+
+ if (ptr[0] != HDMI_INFOFRAME_TYPE_VENDOR ||
+ ptr[1] != 1 ||
+ (ptr[2] != 5 && ptr[2] != 6))
+ return -EINVAL;
+
+ length = ptr[2];
+
+ if (hdmi_infoframe_checksum(buffer,
+ HDMI_INFOFRAME_HEADER_SIZE + length) != 0)
+ return -EINVAL;
+
+ ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+ /* HDMI OUI */
+ if ((ptr[0] != 0x03) ||
+ (ptr[1] != 0x0c) ||
+ (ptr[2] != 0x00))
+ return -EINVAL;
+
+ hdmi_video_format = ptr[3] >> 5;
+
+ if (hdmi_video_format > 0x2)
+ return -EINVAL;
+
+ ret = hdmi_vendor_infoframe_init(hvf);
+ if (ret)
+ return ret;
+
+ hvf->length = length;
+
+ if (hdmi_video_format == 0x1) {
+ hvf->vic = ptr[4];
+ } else if (hdmi_video_format == 0x2) {
+ hvf->s3d_struct = ptr[4] >> 4;
+ if (hvf->s3d_struct >= HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF) {
+ if (length == 6)
+ hvf->s3d_ext_data = ptr[5] >> 4;
+ else
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * hdmi_infoframe_unpack() - unpack binary buffer to a HDMI infoframe
+ * @buffer: source buffer
+ * @frame: HDMI infoframe
+ *
+ * Unpacks the information contained in binary buffer @buffer into a structured
+ * @frame of a HDMI infoframe.
+ * Also verifies the checksum as required by section 5.3.5 of the HDMI 1.4
+ * specification.
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+int hdmi_infoframe_unpack(union hdmi_infoframe *frame, void *buffer)
+{
+ int ret;
+ uint8_t *ptr = buffer;
+
+ switch (ptr[0]) {
+ case HDMI_INFOFRAME_TYPE_AVI:
+ ret = hdmi_avi_infoframe_unpack(&frame->avi, buffer);
+ break;
+ case HDMI_INFOFRAME_TYPE_SPD:
+ ret = hdmi_spd_infoframe_unpack(&frame->spd, buffer);
+ break;
+ case HDMI_INFOFRAME_TYPE_AUDIO:
+ ret = hdmi_audio_infoframe_unpack(&frame->audio, buffer);
+ break;
+ case HDMI_INFOFRAME_TYPE_VENDOR:
+ ret = hdmi_vendor_any_infoframe_unpack(&frame->vendor, buffer);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(hdmi_infoframe_unpack);
diff --git a/sys/arm/nvidia/drm2/hdmi.h b/sys/arm/nvidia/drm2/hdmi.h
new file mode 100644
index 000000000000..df9831546428
--- /dev/null
+++ b/sys/arm/nvidia/drm2/hdmi.h
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2012 Avionic Design GmbH
+ *
+ * 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, sub license,
+ * 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 (including the
+ * next paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _HDMI_H_
+#define _HDMI_H_
+
+enum hdmi_infoframe_type {
+ HDMI_INFOFRAME_TYPE_VENDOR = 0x81,
+ HDMI_INFOFRAME_TYPE_AVI = 0x82,
+ HDMI_INFOFRAME_TYPE_SPD = 0x83,
+ HDMI_INFOFRAME_TYPE_AUDIO = 0x84,
+};
+
+#define HDMI_IEEE_OUI 0x000c03
+#define HDMI_INFOFRAME_HEADER_SIZE 4
+#define HDMI_AVI_INFOFRAME_SIZE 13
+#define HDMI_SPD_INFOFRAME_SIZE 25
+#define HDMI_AUDIO_INFOFRAME_SIZE 10
+
+#define HDMI_INFOFRAME_SIZE(type) \
+ (HDMI_INFOFRAME_HEADER_SIZE + HDMI_ ## type ## _INFOFRAME_SIZE)
+
+struct hdmi_any_infoframe {
+ enum hdmi_infoframe_type type;
+ unsigned char version;
+ unsigned char length;
+};
+
+enum hdmi_colorspace {
+ HDMI_COLORSPACE_RGB,
+ HDMI_COLORSPACE_YUV422,
+ HDMI_COLORSPACE_YUV444,
+ HDMI_COLORSPACE_YUV420,
+ HDMI_COLORSPACE_RESERVED4,
+ HDMI_COLORSPACE_RESERVED5,
+ HDMI_COLORSPACE_RESERVED6,
+ HDMI_COLORSPACE_IDO_DEFINED,
+};
+
+enum hdmi_scan_mode {
+ HDMI_SCAN_MODE_NONE,
+ HDMI_SCAN_MODE_OVERSCAN,
+ HDMI_SCAN_MODE_UNDERSCAN,
+ HDMI_SCAN_MODE_RESERVED,
+};
+
+enum hdmi_colorimetry {
+ HDMI_COLORIMETRY_NONE,
+ HDMI_COLORIMETRY_ITU_601,
+ HDMI_COLORIMETRY_ITU_709,
+ HDMI_COLORIMETRY_EXTENDED,
+};
+
+enum hdmi_picture_aspect {
+ HDMI_PICTURE_ASPECT_NONE,
+ HDMI_PICTURE_ASPECT_4_3,
+ HDMI_PICTURE_ASPECT_16_9,
+ HDMI_PICTURE_ASPECT_RESERVED,
+};
+
+enum hdmi_active_aspect {
+ HDMI_ACTIVE_ASPECT_16_9_TOP = 2,
+ HDMI_ACTIVE_ASPECT_14_9_TOP = 3,
+ HDMI_ACTIVE_ASPECT_16_9_CENTER = 4,
+ HDMI_ACTIVE_ASPECT_PICTURE = 8,
+ HDMI_ACTIVE_ASPECT_4_3 = 9,
+ HDMI_ACTIVE_ASPECT_16_9 = 10,
+ HDMI_ACTIVE_ASPECT_14_9 = 11,
+ HDMI_ACTIVE_ASPECT_4_3_SP_14_9 = 13,
+ HDMI_ACTIVE_ASPECT_16_9_SP_14_9 = 14,
+ HDMI_ACTIVE_ASPECT_16_9_SP_4_3 = 15,
+};
+
+enum hdmi_extended_colorimetry {
+ HDMI_EXTENDED_COLORIMETRY_XV_YCC_601,
+ HDMI_EXTENDED_COLORIMETRY_XV_YCC_709,
+ HDMI_EXTENDED_COLORIMETRY_S_YCC_601,
+ HDMI_EXTENDED_COLORIMETRY_ADOBE_YCC_601,
+ HDMI_EXTENDED_COLORIMETRY_ADOBE_RGB,
+
+ /* The following EC values are only defined in CEA-861-F. */
+ HDMI_EXTENDED_COLORIMETRY_BT2020_CONST_LUM,
+ HDMI_EXTENDED_COLORIMETRY_BT2020,
+ HDMI_EXTENDED_COLORIMETRY_RESERVED,
+};
+
+enum hdmi_quantization_range {
+ HDMI_QUANTIZATION_RANGE_DEFAULT,
+ HDMI_QUANTIZATION_RANGE_LIMITED,
+ HDMI_QUANTIZATION_RANGE_FULL,
+ HDMI_QUANTIZATION_RANGE_RESERVED,
+};
+
+/* non-uniform picture scaling */
+enum hdmi_nups {
+ HDMI_NUPS_UNKNOWN,
+ HDMI_NUPS_HORIZONTAL,
+ HDMI_NUPS_VERTICAL,
+ HDMI_NUPS_BOTH,
+};
+
+enum hdmi_ycc_quantization_range {
+ HDMI_YCC_QUANTIZATION_RANGE_LIMITED,
+ HDMI_YCC_QUANTIZATION_RANGE_FULL,
+};
+
+enum hdmi_content_type {
+ HDMI_CONTENT_TYPE_GRAPHICS,
+ HDMI_CONTENT_TYPE_PHOTO,
+ HDMI_CONTENT_TYPE_CINEMA,
+ HDMI_CONTENT_TYPE_GAME,
+};
+
+struct hdmi_avi_infoframe {
+ enum hdmi_infoframe_type type;
+ unsigned char version;
+ unsigned char length;
+ enum hdmi_colorspace colorspace;
+ enum hdmi_scan_mode scan_mode;
+ enum hdmi_colorimetry colorimetry;
+ enum hdmi_picture_aspect picture_aspect;
+ enum hdmi_active_aspect active_aspect;
+ bool itc;
+ enum hdmi_extended_colorimetry extended_colorimetry;
+ enum hdmi_quantization_range quantization_range;
+ enum hdmi_nups nups;
+ unsigned char video_code;
+ enum hdmi_ycc_quantization_range ycc_quantization_range;
+ enum hdmi_content_type content_type;
+ unsigned char pixel_repeat;
+ unsigned short top_bar;
+ unsigned short bottom_bar;
+ unsigned short left_bar;
+ unsigned short right_bar;
+};
+
+int hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame);
+ssize_t hdmi_avi_infoframe_pack(struct hdmi_avi_infoframe *frame, void *buffer,
+ size_t size);
+
+enum hdmi_spd_sdi {
+ HDMI_SPD_SDI_UNKNOWN,
+ HDMI_SPD_SDI_DSTB,
+ HDMI_SPD_SDI_DVDP,
+ HDMI_SPD_SDI_DVHS,
+ HDMI_SPD_SDI_HDDVR,
+ HDMI_SPD_SDI_DVC,
+ HDMI_SPD_SDI_DSC,
+ HDMI_SPD_SDI_VCD,
+ HDMI_SPD_SDI_GAME,
+ HDMI_SPD_SDI_PC,
+ HDMI_SPD_SDI_BD,
+ HDMI_SPD_SDI_SACD,
+ HDMI_SPD_SDI_HDDVD,
+ HDMI_SPD_SDI_PMP,
+};
+
+struct hdmi_spd_infoframe {
+ enum hdmi_infoframe_type type;
+ unsigned char version;
+ unsigned char length;
+ char vendor[8];
+ char product[16];
+ enum hdmi_spd_sdi sdi;
+};
+
+int hdmi_spd_infoframe_init(struct hdmi_spd_infoframe *frame,
+ const char *vendor, const char *product);
+ssize_t hdmi_spd_infoframe_pack(struct hdmi_spd_infoframe *frame, void *buffer,
+ size_t size);
+
+enum hdmi_audio_coding_type {
+ HDMI_AUDIO_CODING_TYPE_STREAM,
+ HDMI_AUDIO_CODING_TYPE_PCM,
+ HDMI_AUDIO_CODING_TYPE_AC3,
+ HDMI_AUDIO_CODING_TYPE_MPEG1,
+ HDMI_AUDIO_CODING_TYPE_MP3,
+ HDMI_AUDIO_CODING_TYPE_MPEG2,
+ HDMI_AUDIO_CODING_TYPE_AAC_LC,
+ HDMI_AUDIO_CODING_TYPE_DTS,
+ HDMI_AUDIO_CODING_TYPE_ATRAC,
+ HDMI_AUDIO_CODING_TYPE_DSD,
+ HDMI_AUDIO_CODING_TYPE_EAC3,
+ HDMI_AUDIO_CODING_TYPE_DTS_HD,
+ HDMI_AUDIO_CODING_TYPE_MLP,
+ HDMI_AUDIO_CODING_TYPE_DST,
+ HDMI_AUDIO_CODING_TYPE_WMA_PRO,
+ HDMI_AUDIO_CODING_TYPE_CXT,
+};
+
+enum hdmi_audio_sample_size {
+ HDMI_AUDIO_SAMPLE_SIZE_STREAM,
+ HDMI_AUDIO_SAMPLE_SIZE_16,
+ HDMI_AUDIO_SAMPLE_SIZE_20,
+ HDMI_AUDIO_SAMPLE_SIZE_24,
+};
+
+enum hdmi_audio_sample_frequency {
+ HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM,
+ HDMI_AUDIO_SAMPLE_FREQUENCY_32000,
+ HDMI_AUDIO_SAMPLE_FREQUENCY_44100,
+ HDMI_AUDIO_SAMPLE_FREQUENCY_48000,
+ HDMI_AUDIO_SAMPLE_FREQUENCY_88200,
+ HDMI_AUDIO_SAMPLE_FREQUENCY_96000,
+ HDMI_AUDIO_SAMPLE_FREQUENCY_176400,
+ HDMI_AUDIO_SAMPLE_FREQUENCY_192000,
+};
+
+enum hdmi_audio_coding_type_ext {
+ /* Refer to Audio Coding Type (CT) field in Data Byte 1 */
+ HDMI_AUDIO_CODING_TYPE_EXT_CT,
+
+ /*
+ * The next three CXT values are defined in CEA-861-E only.
+ * They do not exist in older versions, and in CEA-861-F they are
+ * defined as 'Not in use'.
+ */
+ HDMI_AUDIO_CODING_TYPE_EXT_HE_AAC,
+ HDMI_AUDIO_CODING_TYPE_EXT_HE_AAC_V2,
+ HDMI_AUDIO_CODING_TYPE_EXT_MPEG_SURROUND,
+
+ /* The following CXT values are only defined in CEA-861-F. */
+ HDMI_AUDIO_CODING_TYPE_EXT_MPEG4_HE_AAC,
+ HDMI_AUDIO_CODING_TYPE_EXT_MPEG4_HE_AAC_V2,
+ HDMI_AUDIO_CODING_TYPE_EXT_MPEG4_AAC_LC,
+ HDMI_AUDIO_CODING_TYPE_EXT_DRA,
+ HDMI_AUDIO_CODING_TYPE_EXT_MPEG4_HE_AAC_SURROUND,
+ HDMI_AUDIO_CODING_TYPE_EXT_MPEG4_AAC_LC_SURROUND = 10,
+};
+
+struct hdmi_audio_infoframe {
+ enum hdmi_infoframe_type type;
+ unsigned char version;
+ unsigned char length;
+ unsigned char channels;
+ enum hdmi_audio_coding_type coding_type;
+ enum hdmi_audio_sample_size sample_size;
+ enum hdmi_audio_sample_frequency sample_frequency;
+ enum hdmi_audio_coding_type_ext coding_type_ext;
+ unsigned char channel_allocation;
+ unsigned char level_shift_value;
+ bool downmix_inhibit;
+
+};
+
+int hdmi_audio_infoframe_init(struct hdmi_audio_infoframe *frame);
+ssize_t hdmi_audio_infoframe_pack(struct hdmi_audio_infoframe *frame,
+ void *buffer, size_t size);
+
+enum hdmi_3d_structure {
+ HDMI_3D_STRUCTURE_INVALID = -1,
+ HDMI_3D_STRUCTURE_FRAME_PACKING = 0,
+ HDMI_3D_STRUCTURE_FIELD_ALTERNATIVE,
+ HDMI_3D_STRUCTURE_LINE_ALTERNATIVE,
+ HDMI_3D_STRUCTURE_SIDE_BY_SIDE_FULL,
+ HDMI_3D_STRUCTURE_L_DEPTH,
+ HDMI_3D_STRUCTURE_L_DEPTH_GFX_GFX_DEPTH,
+ HDMI_3D_STRUCTURE_TOP_AND_BOTTOM,
+ HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF = 8,
+};
+
+struct hdmi_vendor_infoframe {
+ enum hdmi_infoframe_type type;
+ unsigned char version;
+ unsigned char length;
+ unsigned int oui;
+ uint8_t vic;
+ enum hdmi_3d_structure s3d_struct;
+ unsigned int s3d_ext_data;
+};
+
+int hdmi_vendor_infoframe_init(struct hdmi_vendor_infoframe *frame);
+ssize_t hdmi_vendor_infoframe_pack(struct hdmi_vendor_infoframe *frame,
+ void *buffer, size_t size);
+
+union hdmi_vendor_any_infoframe {
+ struct {
+ enum hdmi_infoframe_type type;
+ unsigned char version;
+ unsigned char length;
+ unsigned int oui;
+ } any;
+ struct hdmi_vendor_infoframe hdmi;
+};
+
+/**
+ * union hdmi_infoframe - overall union of all abstract infoframe representations
+ * @any: generic infoframe
+ * @avi: avi infoframe
+ * @spd: spd infoframe
+ * @vendor: union of all vendor infoframes
+ * @audio: audio infoframe
+ *
+ * This is used by the generic pack function. This works since all infoframes
+ * have the same header which also indicates which type of infoframe should be
+ * packed.
+ */
+union hdmi_infoframe {
+ struct hdmi_any_infoframe any;
+ struct hdmi_avi_infoframe avi;
+ struct hdmi_spd_infoframe spd;
+ union hdmi_vendor_any_infoframe vendor;
+ struct hdmi_audio_infoframe audio;
+};
+
+ssize_t
+hdmi_infoframe_pack(union hdmi_infoframe *frame, void *buffer, size_t size);
+int hdmi_infoframe_unpack(union hdmi_infoframe *frame, void *buffer);
+void hdmi_infoframe_log(union hdmi_infoframe *frame);
+
+#endif /* _HDMI_H */
diff --git a/sys/arm/nvidia/drm2/tegra_bo.c b/sys/arm/nvidia/drm2/tegra_bo.c
new file mode 100644
index 000000000000..7479fd8bc8da
--- /dev/null
+++ b/sys/arm/nvidia/drm2/tegra_bo.c
@@ -0,0 +1,366 @@
+/*-
+ * Copyright (c) 2015 Michal Meloun
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+
+#include <machine/bus.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/drm2/drmP.h>
+#include <dev/drm2/drm_crtc_helper.h>
+#include <dev/drm2/drm_fb_helper.h>
+
+#include <arm/nvidia/drm2/tegra_drm.h>
+
+#include <sys/vmem.h>
+#include <sys/vmem.h>
+#include <vm/vm.h>
+#include <vm/vm_pageout.h>
+
+static void
+tegra_bo_destruct(struct tegra_bo *bo)
+{
+ vm_page_t m;
+ size_t size;
+ int i;
+
+ if (bo->cdev_pager == NULL)
+ return;
+
+ size = round_page(bo->gem_obj.size);
+ if (bo->vbase != 0)
+ pmap_qremove(bo->vbase, bo->npages);
+
+ VM_OBJECT_WLOCK(bo->cdev_pager);
+ for (i = 0; i < bo->npages; i++) {
+ m = bo->m[i];
+ vm_page_busy_acquire(m, 0);
+ cdev_pager_free_page(bo->cdev_pager, m);
+ m->flags &= ~PG_FICTITIOUS;
+ vm_page_unwire_noq(m);
+ vm_page_free(m);
+ }
+ VM_OBJECT_WUNLOCK(bo->cdev_pager);
+
+ vm_object_deallocate(bo->cdev_pager);
+ if (bo->vbase != 0)
+ vmem_free(kmem_arena, bo->vbase, size);
+}
+
+static void
+tegra_bo_free_object(struct drm_gem_object *gem_obj)
+{
+ struct tegra_bo *bo;
+
+ bo = container_of(gem_obj, struct tegra_bo, gem_obj);
+ drm_gem_free_mmap_offset(gem_obj);
+ drm_gem_object_release(gem_obj);
+
+ tegra_bo_destruct(bo);
+
+ free(bo->m, DRM_MEM_DRIVER);
+ free(bo, DRM_MEM_DRIVER);
+}
+
+static int
+tegra_bo_alloc_contig(size_t npages, u_long alignment, vm_memattr_t memattr,
+ vm_page_t **ret_page)
+{
+ vm_page_t m;
+ int pflags, tries, i;
+ vm_paddr_t low, high, boundary;
+
+ low = 0;
+ high = -1UL;
+ boundary = 0;
+ pflags = VM_ALLOC_NORMAL | VM_ALLOC_NOOBJ | VM_ALLOC_NOBUSY |
+ VM_ALLOC_WIRED | VM_ALLOC_ZERO;
+ tries = 0;
+retry:
+ m = vm_page_alloc_contig(NULL, 0, pflags, npages, low, high, alignment,
+ boundary, memattr);
+ if (m == NULL) {
+ if (tries < 3) {
+ if (!vm_page_reclaim_contig(pflags, npages, low, high,
+ alignment, boundary))
+ vm_wait(NULL);
+ tries++;
+ goto retry;
+ }
+ return (ENOMEM);
+ }
+
+ for (i = 0; i < npages; i++, m++) {
+ if ((m->flags & PG_ZERO) == 0)
+ pmap_zero_page(m);
+ m->valid = VM_PAGE_BITS_ALL;
+ (*ret_page)[i] = m;
+ }
+
+ return (0);
+}
+
+/* Initialize pager and insert all object pages to it*/
+static int
+tegra_bo_init_pager(struct tegra_bo *bo)
+{
+ vm_page_t m;
+ size_t size;
+ int i;
+
+ size = round_page(bo->gem_obj.size);
+
+ bo->pbase = VM_PAGE_TO_PHYS(bo->m[0]);
+ if (vmem_alloc(kmem_arena, size, M_WAITOK | M_BESTFIT, &bo->vbase))
+ return (ENOMEM);
+
+ VM_OBJECT_WLOCK(bo->cdev_pager);
+ for (i = 0; i < bo->npages; i++) {
+ m = bo->m[i];
+ /*
+ * XXX This is a temporary hack.
+ * We need pager suitable for paging (mmap) managed
+ * real (non-fictitious) pages.
+ * - managed pages are needed for clean module unload.
+ * - aliasing fictitious page to real one is bad,
+ * pmap cannot handle this situation without issues
+ * It expects that
+ * paddr = PHYS_TO_VM_PAGE(VM_PAGE_TO_PHYS(paddr))
+ * for every single page passed to pmap.
+ */
+ m->oflags &= ~VPO_UNMANAGED;
+ m->flags |= PG_FICTITIOUS;
+ if (vm_page_insert(m, bo->cdev_pager, i) != 0)
+ return (EINVAL);
+ }
+ VM_OBJECT_WUNLOCK(bo->cdev_pager);
+
+ pmap_qenter(bo->vbase, bo->m, bo->npages);
+ return (0);
+}
+
+/* Allocate memory for frame buffer */
+static int
+tegra_bo_alloc(struct drm_device *drm, struct tegra_bo *bo)
+{
+ size_t size;
+ int rv;
+
+ size = bo->gem_obj.size;
+
+ bo->npages = atop(size);
+ bo->m = malloc(sizeof(vm_page_t *) * bo->npages, DRM_MEM_DRIVER,
+ M_WAITOK | M_ZERO);
+
+ rv = tegra_bo_alloc_contig(bo->npages, PAGE_SIZE,
+ VM_MEMATTR_WRITE_COMBINING, &(bo->m));
+ if (rv != 0) {
+ DRM_WARNING("Cannot allocate memory for gem object.\n");
+ return (rv);
+ }
+ rv = tegra_bo_init_pager(bo);
+ if (rv != 0) {
+ DRM_WARNING("Cannot initialize gem object pager.\n");
+ return (rv);
+ }
+ return (0);
+}
+
+int
+tegra_bo_create(struct drm_device *drm, size_t size, struct tegra_bo **res_bo)
+{
+ struct tegra_bo *bo;
+ int rv;
+
+ if (size <= 0)
+ return (-EINVAL);
+
+ bo = malloc(sizeof(*bo), DRM_MEM_DRIVER, M_WAITOK | M_ZERO);
+
+ size = round_page(size);
+ rv = drm_gem_object_init(drm, &bo->gem_obj, size);
+ if (rv != 0) {
+ free(bo, DRM_MEM_DRIVER);
+ return (rv);
+ }
+ rv = drm_gem_create_mmap_offset(&bo->gem_obj);
+ if (rv != 0) {
+ drm_gem_object_release(&bo->gem_obj);
+ free(bo, DRM_MEM_DRIVER);
+ return (rv);
+ }
+
+ bo->cdev_pager = cdev_pager_allocate(&bo->gem_obj, OBJT_MGTDEVICE,
+ drm->driver->gem_pager_ops, size, 0, 0, NULL);
+ rv = tegra_bo_alloc(drm, bo);
+ if (rv != 0) {
+ tegra_bo_free_object(&bo->gem_obj);
+ return (rv);
+ }
+
+ *res_bo = bo;
+ return (0);
+}
+
+static int
+tegra_bo_create_with_handle(struct drm_file *file, struct drm_device *drm,
+ size_t size, uint32_t *handle, struct tegra_bo **res_bo)
+{
+ int rv;
+ struct tegra_bo *bo;
+
+ rv = tegra_bo_create(drm, size, &bo);
+ if (rv != 0)
+ return (rv);
+
+ rv = drm_gem_handle_create(file, &bo->gem_obj, handle);
+ if (rv != 0) {
+ tegra_bo_free_object(&bo->gem_obj);
+ drm_gem_object_release(&bo->gem_obj);
+ return (rv);
+ }
+
+ drm_gem_object_unreference_unlocked(&bo->gem_obj);
+
+ *res_bo = bo;
+ return (0);
+}
+
+static int
+tegra_bo_dumb_create(struct drm_file *file, struct drm_device *drm_dev,
+ struct drm_mode_create_dumb *args)
+{
+ struct tegra_drm *drm;
+ struct tegra_bo *bo;
+ int rv;
+
+ drm = container_of(drm_dev, struct tegra_drm, drm_dev);
+
+ args->pitch= (args->width * args->bpp + 7) / 8;
+ args->pitch = roundup(args->pitch, drm->pitch_align);
+ args->size = args->pitch * args->height;
+ rv = tegra_bo_create_with_handle(file, drm_dev, args->size,
+ &args->handle, &bo);
+
+ return (rv);
+}
+
+static int
+tegra_bo_dumb_map_offset(struct drm_file *file_priv,
+ struct drm_device *drm_dev, uint32_t handle, uint64_t *offset)
+{
+ struct drm_gem_object *gem_obj;
+ int rv;
+
+ DRM_LOCK(drm_dev);
+ gem_obj = drm_gem_object_lookup(drm_dev, file_priv, handle);
+ if (gem_obj == NULL) {
+ device_printf(drm_dev->dev, "Object not found\n");
+ DRM_UNLOCK(drm_dev);
+ return (-EINVAL);
+ }
+ rv = drm_gem_create_mmap_offset(gem_obj);
+ if (rv != 0)
+ goto fail;
+
+ *offset = DRM_GEM_MAPPING_OFF(gem_obj->map_list.key) |
+ DRM_GEM_MAPPING_KEY;
+
+ drm_gem_object_unreference(gem_obj);
+ DRM_UNLOCK(drm_dev);
+ return (0);
+
+fail:
+ drm_gem_object_unreference(gem_obj);
+ DRM_UNLOCK(drm_dev);
+ return (rv);
+}
+
+static int
+tegra_bo_dumb_destroy(struct drm_file *file_priv, struct drm_device *drm_dev,
+ unsigned int handle)
+{
+ int rv;
+
+ rv = drm_gem_handle_delete(file_priv, handle);
+ return (rv);
+}
+
+/*
+ * mmap support
+ */
+static int
+tegra_gem_pager_fault(vm_object_t vm_obj, vm_ooffset_t offset, int prot,
+ vm_page_t *mres)
+{
+
+#ifdef DRM_PAGER_DEBUG
+ DRM_DEBUG("object %p offset %jd prot %d mres %p\n",
+ vm_obj, (intmax_t)offset, prot, mres);
+#endif
+ return (VM_PAGER_FAIL);
+
+}
+
+static int
+tegra_gem_pager_ctor(void *handle, vm_ooffset_t size, vm_prot_t prot,
+ vm_ooffset_t foff, struct ucred *cred, u_short *color)
+{
+
+ if (color != NULL)
+ *color = 0;
+ return (0);
+}
+
+static void
+tegra_gem_pager_dtor(void *handle)
+{
+
+}
+
+static struct cdev_pager_ops tegra_gem_pager_ops = {
+ .cdev_pg_fault = tegra_gem_pager_fault,
+ .cdev_pg_ctor = tegra_gem_pager_ctor,
+ .cdev_pg_dtor = tegra_gem_pager_dtor
+};
+
+/* Fill up relevant fields in drm_driver ops */
+void
+tegra_bo_driver_register(struct drm_driver *drm_drv)
+{
+ drm_drv->gem_free_object = tegra_bo_free_object;
+ drm_drv->gem_pager_ops = &tegra_gem_pager_ops;
+ drm_drv->dumb_create = tegra_bo_dumb_create;
+ drm_drv->dumb_map_offset = tegra_bo_dumb_map_offset;
+ drm_drv->dumb_destroy = tegra_bo_dumb_destroy;
+}
diff --git a/sys/arm/nvidia/drm2/tegra_dc.c b/sys/arm/nvidia/drm2/tegra_dc.c
new file mode 100644
index 000000000000..a5e661dab6b2
--- /dev/null
+++ b/sys/arm/nvidia/drm2/tegra_dc.c
@@ -0,0 +1,1441 @@
+/*-
+ * Copyright (c) 2015 Michal Meloun
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/gpio.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/malloc.h>
+#include <sys/rman.h>
+#include <sys/sysctl.h>
+
+#include <machine/bus.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+#include <dev/drm2/drmP.h>
+#include <dev/drm2/drm_crtc_helper.h>
+#include <dev/drm2/drm_fb_helper.h>
+#include <dev/drm2/drm_fixed.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <arm/nvidia/drm2/tegra_dc_reg.h>
+#include <arm/nvidia/drm2/tegra_drm.h>
+#include <arm/nvidia/tegra_pmc.h>
+
+#include "tegra_drm_if.h"
+#include "tegra_dc_if.h"
+
+#define WR4(_sc, _r, _v) bus_write_4((_sc)->mem_res, 4 * (_r), (_v))
+#define RD4(_sc, _r) bus_read_4((_sc)->mem_res, 4 * (_r))
+
+#define LOCK(_sc) mtx_lock(&(_sc)->mtx)
+#define UNLOCK(_sc) mtx_unlock(&(_sc)->mtx)
+#define SLEEP(_sc, timeout) \
+ mtx_sleep(sc, &sc->mtx, 0, "tegra_dc_wait", timeout);
+#define LOCK_INIT(_sc) \
+ mtx_init(&_sc->mtx, device_get_nameunit(_sc->dev), "tegra_dc", MTX_DEF)
+#define LOCK_DESTROY(_sc) mtx_destroy(&_sc->mtx)
+#define ASSERT_LOCKED(_sc) mtx_assert(&_sc->mtx, MA_OWNED)
+#define ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->mtx, MA_NOTOWNED)
+
+#define SYNCPT_VBLANK0 26
+#define SYNCPT_VBLANK1 27
+
+#define DC_MAX_PLANES 2 /* Maximum planes */
+
+/* DRM Formats supported by DC */
+/* XXXX expand me */
+static uint32_t dc_plane_formats[] = {
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_YUV420,
+ DRM_FORMAT_YUV422,
+};
+
+/* Complete description of one window (plane) */
+struct dc_window {
+ /* Source (in framebuffer) rectangle, in pixels */
+ u_int src_x;
+ u_int src_y;
+ u_int src_w;
+ u_int src_h;
+
+ /* Destination (on display) rectangle, in pixels */
+ u_int dst_x;
+ u_int dst_y;
+ u_int dst_w;
+ u_int dst_h;
+
+ /* Parsed pixel format */
+ u_int bits_per_pixel;
+ bool is_yuv; /* any YUV mode */
+ bool is_yuv_planar; /* planar YUV mode */
+ uint32_t color_mode; /* DC_WIN_COLOR_DEPTH */
+ uint32_t swap; /* DC_WIN_BYTE_SWAP */
+ uint32_t surface_kind; /* DC_WINBUF_SURFACE_KIND */
+ uint32_t block_height; /* DC_WINBUF_SURFACE_KIND */
+
+ /* Parsed flipping, rotation is not supported for pitched modes */
+ bool flip_x; /* inverted X-axis */
+ bool flip_y; /* inverted Y-axis */
+ bool transpose_xy; /* swap X and Y-axis */
+
+ /* Color planes base addresses and strides */
+ bus_size_t base[3];
+ uint32_t stride[3]; /* stride[2] isn't used by HW */
+};
+
+struct dc_softc {
+ device_t dev;
+ struct resource *mem_res;
+ struct resource *irq_res;
+ void *irq_ih;
+ struct mtx mtx;
+
+ clk_t clk_parent;
+ clk_t clk_dc;
+ hwreset_t hwreset_dc;
+
+ int pitch_align;
+
+ struct tegra_crtc tegra_crtc;
+ struct drm_pending_vblank_event *event;
+ struct drm_gem_object *cursor_gem;
+};
+
+static struct ofw_compat_data compat_data[] = {
+ {"nvidia,tegra124-dc", 1},
+ {NULL, 0},
+};
+
+/* Convert standard drm pixel format to tegra windows parameters. */
+static int
+dc_parse_drm_format(struct tegra_fb *fb, struct dc_window *win)
+{
+ struct tegra_bo *bo;
+ uint32_t cm;
+ uint32_t sw;
+ bool is_yuv, is_yuv_planar;
+ int nplanes, i;
+
+ switch (fb->drm_fb.pixel_format) {
+ case DRM_FORMAT_XBGR8888:
+ sw = BYTE_SWAP(NOSWAP);
+ cm = WIN_COLOR_DEPTH_R8G8B8A8;
+ is_yuv = false;
+ is_yuv_planar = false;
+ break;
+
+ case DRM_FORMAT_XRGB8888:
+ sw = BYTE_SWAP(NOSWAP);
+ cm = WIN_COLOR_DEPTH_B8G8R8A8;
+ is_yuv = false;
+ is_yuv_planar = false;
+ break;
+
+ case DRM_FORMAT_RGB565:
+ sw = BYTE_SWAP(NOSWAP);
+ cm = WIN_COLOR_DEPTH_B5G6R5;
+ is_yuv = false;
+ is_yuv_planar = false;
+ break;
+
+ case DRM_FORMAT_UYVY:
+ sw = BYTE_SWAP(NOSWAP);
+ cm = WIN_COLOR_DEPTH_YCbCr422;
+ is_yuv = true;
+ is_yuv_planar = false;
+ break;
+
+ case DRM_FORMAT_YUYV:
+ sw = BYTE_SWAP(SWAP2);
+ cm = WIN_COLOR_DEPTH_YCbCr422;
+ is_yuv = true;
+ is_yuv_planar = false;
+ break;
+
+ case DRM_FORMAT_YUV420:
+ sw = BYTE_SWAP(NOSWAP);
+ cm = WIN_COLOR_DEPTH_YCbCr420P;
+ is_yuv = true;
+ is_yuv_planar = true;
+ break;
+
+ case DRM_FORMAT_YUV422:
+ sw = BYTE_SWAP(NOSWAP);
+ cm = WIN_COLOR_DEPTH_YCbCr422P;
+ is_yuv = true;
+ is_yuv_planar = true;
+ break;
+
+ default:
+ /* Unsupported format */
+ return (-EINVAL);
+ }
+
+ /* Basic check of arguments. */
+ switch (fb->rotation) {
+ case 0:
+ case 180:
+ break;
+
+ case 90: /* Rotation is supported only */
+ case 270: /* for block linear surfaces */
+ if (!fb->block_linear)
+ return (-EINVAL);
+ break;
+
+ default:
+ return (-EINVAL);
+ }
+ /* XXX Add more checks (sizes, scaling...) */
+
+ if (win == NULL)
+ return (0);
+
+ win->surface_kind =
+ fb->block_linear ? SURFACE_KIND_BL_16B2: SURFACE_KIND_PITCH;
+ win->block_height = fb->block_height;
+ switch (fb->rotation) {
+ case 0: /* (0,0,0) */
+ win->transpose_xy = false;
+ win->flip_x = false;
+ win->flip_y = false;
+ break;
+
+ case 90: /* (1,0,1) */
+ win->transpose_xy = true;
+ win->flip_x = false;
+ win->flip_y = true;
+ break;
+
+ case 180: /* (0,1,1) */
+ win->transpose_xy = false;
+ win->flip_x = true;
+ win->flip_y = true;
+ break;
+
+ case 270: /* (1,1,0) */
+ win->transpose_xy = true;
+ win->flip_x = true;
+ win->flip_y = false;
+ break;
+ }
+ win->flip_x ^= fb->flip_x;
+ win->flip_y ^= fb->flip_y;
+
+ win->color_mode = cm;
+ win->swap = sw;
+ win->bits_per_pixel = fb->drm_fb.bits_per_pixel;
+ win->is_yuv = is_yuv;
+ win->is_yuv_planar = is_yuv_planar;
+
+ nplanes = drm_format_num_planes(fb->drm_fb.pixel_format);
+ for (i = 0; i < nplanes; i++) {
+ bo = fb->planes[i];
+ win->base[i] = bo->pbase + fb->drm_fb.offsets[i];
+ win->stride[i] = fb->drm_fb.pitches[i];
+ }
+ return (0);
+}
+
+/*
+ * Scaling functions.
+ *
+ * It's unclear if we want/must program the fractional portion
+ * (aka bias) of init_dda registers, mainly when mirrored axis
+ * modes are used.
+ * For now, we use 1.0 as recommended by TRM.
+ */
+static inline uint32_t
+dc_scaling_init(uint32_t start)
+{
+
+ return (1 << 12);
+}
+
+static inline uint32_t
+dc_scaling_incr(uint32_t src, uint32_t dst, uint32_t maxscale)
+{
+ uint32_t val;
+
+ val = (src - 1) << 12 ; /* 4.12 fixed float */
+ val /= (dst - 1);
+ if (val > (maxscale << 12))
+ val = maxscale << 12;
+ return val;
+}
+
+/* -------------------------------------------------------------------
+ *
+ * HW Access.
+ *
+ */
+
+/*
+ * Setup pixel clock.
+ * Minimal frequency is pixel clock, but output is free to select
+ * any higher.
+ */
+static int
+dc_setup_clk(struct dc_softc *sc, struct drm_crtc *crtc,
+ struct drm_display_mode *mode, uint32_t *div)
+{
+ uint64_t pclk, freq;
+ struct tegra_drm_encoder *output;
+ struct drm_encoder *encoder;
+ long rv;
+
+ pclk = mode->clock * 1000;
+
+ /* Find attached encoder */
+ output = NULL;
+ list_for_each_entry(encoder, &crtc->dev->mode_config.encoder_list,
+ head) {
+ if (encoder->crtc == crtc) {
+ output = container_of(encoder, struct tegra_drm_encoder,
+ encoder);
+ break;
+ }
+ }
+ if (output == NULL)
+ return (-ENODEV);
+
+ if (output->setup_clock == NULL)
+ panic("Output have not setup_clock function.\n");
+ rv = output->setup_clock(output, sc->clk_dc, pclk);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot setup pixel clock: %llu\n",
+ pclk);
+ return (rv);
+ }
+
+ rv = clk_get_freq(sc->clk_dc, &freq);
+ *div = (freq * 2 / pclk) - 2;
+
+ DRM_DEBUG_KMS("frequency: %llu, DC divider: %u\n", freq, *div);
+
+ return 0;
+}
+
+static void
+dc_setup_window(struct dc_softc *sc, unsigned int index, struct dc_window *win)
+{
+ uint32_t h_offset, v_offset, h_size, v_size, bpp;
+ uint32_t h_init_dda, v_init_dda, h_incr_dda, v_incr_dda;
+ uint32_t val;
+
+#ifdef DMR_DEBUG_WINDOW
+ printf("%s window: %d\n", __func__, index);
+ printf(" src: x: %d, y: %d, w: %d, h: %d\n",
+ win->src_x, win->src_y, win->src_w, win->src_h);
+ printf(" dst: x: %d, y: %d, w: %d, h: %d\n",
+ win->dst_x, win->dst_y, win->dst_w, win->dst_h);
+ printf(" bpp: %d, color_mode: %d, swap: %d\n",
+ win->bits_per_pixel, win->color_mode, win->swap);
+#endif
+
+ if (win->is_yuv)
+ bpp = win->is_yuv_planar ? 1 : 2;
+ else
+ bpp = (win->bits_per_pixel + 7) / 8;
+
+ if (!win->transpose_xy) {
+ h_size = win->src_w * bpp;
+ v_size = win->src_h;
+ } else {
+ h_size = win->src_h * bpp;
+ v_size = win->src_w;
+ }
+
+ h_offset = win->src_x * bpp;
+ v_offset = win->src_y;
+ if (win->flip_x) {
+ h_offset += win->src_w * bpp - 1;
+ }
+ if (win->flip_y)
+ v_offset += win->src_h - 1;
+
+ /* Adjust offsets for planar yuv modes */
+ if (win->is_yuv_planar) {
+ h_offset &= ~1;
+ if (win->flip_x )
+ h_offset |= 1;
+ v_offset &= ~1;
+ if (win->flip_y )
+ v_offset |= 1;
+ }
+
+ /* Setup scaling. */
+ if (!win->transpose_xy) {
+ h_init_dda = dc_scaling_init(win->src_x);
+ v_init_dda = dc_scaling_init(win->src_y);
+ h_incr_dda = dc_scaling_incr(win->src_w, win->dst_w, 4);
+ v_incr_dda = dc_scaling_incr(win->src_h, win->dst_h, 15);
+ } else {
+ h_init_dda = dc_scaling_init(win->src_y);
+ v_init_dda = dc_scaling_init(win->src_x);
+ h_incr_dda = dc_scaling_incr(win->src_h, win->dst_h, 4);
+ v_incr_dda = dc_scaling_incr(win->src_w, win->dst_w, 15);
+ }
+#ifdef DMR_DEBUG_WINDOW
+ printf("\n");
+ printf(" bpp: %d, size: h: %d v: %d, offset: h:%d v: %d\n",
+ bpp, h_size, v_size, h_offset, v_offset);
+ printf(" init_dda: h: %d v: %d, incr_dda: h: %d v: %d\n",
+ h_init_dda, v_init_dda, h_incr_dda, v_incr_dda);
+#endif
+
+ LOCK(sc);
+
+ /* Select target window */
+ val = WINDOW_A_SELECT << index;
+ WR4(sc, DC_CMD_DISPLAY_WINDOW_HEADER, val);
+
+ /* Sizes */
+ WR4(sc, DC_WIN_POSITION, WIN_POSITION(win->dst_x, win->dst_y));
+ WR4(sc, DC_WIN_SIZE, WIN_SIZE(win->dst_w, win->dst_h));
+ WR4(sc, DC_WIN_PRESCALED_SIZE, WIN_PRESCALED_SIZE(h_size, v_size));
+
+ /* DDA */
+ WR4(sc, DC_WIN_DDA_INCREMENT,
+ WIN_DDA_INCREMENT(h_incr_dda, v_incr_dda));
+ WR4(sc, DC_WIN_H_INITIAL_DDA, h_init_dda);
+ WR4(sc, DC_WIN_V_INITIAL_DDA, v_init_dda);
+
+ /* Color planes base addresses and strides */
+ WR4(sc, DC_WINBUF_START_ADDR, win->base[0]);
+ if (win->is_yuv_planar) {
+ WR4(sc, DC_WINBUF_START_ADDR_U, win->base[1]);
+ WR4(sc, DC_WINBUF_START_ADDR_V, win->base[2]);
+ WR4(sc, DC_WIN_LINE_STRIDE,
+ win->stride[1] << 16 | win->stride[0]);
+ } else {
+ WR4(sc, DC_WIN_LINE_STRIDE, win->stride[0]);
+ }
+
+ /* Offsets for rotation and axis flip */
+ WR4(sc, DC_WINBUF_ADDR_H_OFFSET, h_offset);
+ WR4(sc, DC_WINBUF_ADDR_V_OFFSET, v_offset);
+
+ /* Color format */
+ WR4(sc, DC_WIN_COLOR_DEPTH, win->color_mode);
+ WR4(sc, DC_WIN_BYTE_SWAP, win->swap);
+
+ /* Tiling */
+ val = win->surface_kind;
+ if (win->surface_kind == SURFACE_KIND_BL_16B2)
+ val |= SURFACE_KIND_BLOCK_HEIGHT(win->block_height);
+ WR4(sc, DC_WINBUF_SURFACE_KIND, val);
+
+ /* Color space coefs for YUV modes */
+ if (win->is_yuv) {
+ WR4(sc, DC_WINC_CSC_YOF, 0x00f0);
+ WR4(sc, DC_WINC_CSC_KYRGB, 0x012a);
+ WR4(sc, DC_WINC_CSC_KUR, 0x0000);
+ WR4(sc, DC_WINC_CSC_KVR, 0x0198);
+ WR4(sc, DC_WINC_CSC_KUG, 0x039b);
+ WR4(sc, DC_WINC_CSC_KVG, 0x032f);
+ WR4(sc, DC_WINC_CSC_KUB, 0x0204);
+ WR4(sc, DC_WINC_CSC_KVB, 0x0000);
+ }
+
+ val = WIN_ENABLE;
+ if (win->is_yuv)
+ val |= CSC_ENABLE;
+ else if (win->bits_per_pixel < 24)
+ val |= COLOR_EXPAND;
+ if (win->flip_y)
+ val |= V_DIRECTION;
+ if (win->flip_x)
+ val |= H_DIRECTION;
+ if (win->transpose_xy)
+ val |= SCAN_COLUMN;
+ WR4(sc, DC_WINC_WIN_OPTIONS, val);
+
+#ifdef DMR_DEBUG_WINDOW
+ /* Set underflow debug mode -> highlight missing pixels. */
+ WR4(sc, DC_WINBUF_UFLOW_CTRL, UFLOW_CTR_ENABLE);
+ WR4(sc, DC_WINBUF_UFLOW_DBG_PIXEL, 0xFFFF0000);
+#endif
+
+ UNLOCK(sc);
+}
+
+/* -------------------------------------------------------------------
+ *
+ * Plane functions.
+ *
+ */
+static int
+dc_plane_update(struct drm_plane *drm_plane, struct drm_crtc *drm_crtc,
+ struct drm_framebuffer *drm_fb,
+ int crtc_x, int crtc_y, unsigned int crtc_w, unsigned int crtc_h,
+ uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h)
+{
+ struct tegra_plane *plane;
+ struct tegra_crtc *crtc;
+ struct tegra_fb *fb;
+ struct dc_softc *sc;
+ struct dc_window win;
+ int rv;
+
+ plane = container_of(drm_plane, struct tegra_plane, drm_plane);
+ fb = container_of(drm_fb, struct tegra_fb, drm_fb);
+ crtc = container_of(drm_crtc, struct tegra_crtc, drm_crtc);
+ sc = device_get_softc(crtc->dev);
+
+ memset(&win, 0, sizeof(win));
+ win.src_x = src_x >> 16;
+ win.src_y = src_y >> 16;
+ win.src_w = src_w >> 16;
+ win.src_h = src_h >> 16;
+ win.dst_x = crtc_x;
+ win.dst_y = crtc_y;
+ win.dst_w = crtc_w;
+ win.dst_h = crtc_h;
+
+ rv = dc_parse_drm_format(fb, &win);
+ if (rv != 0) {
+ DRM_WARNING("unsupported pixel format %d\n",
+ fb->drm_fb.pixel_format);
+ return (rv);
+ }
+
+ dc_setup_window(sc, plane->index, &win);
+
+ WR4(sc, DC_CMD_STATE_CONTROL, WIN_A_UPDATE << plane->index);
+ WR4(sc, DC_CMD_STATE_CONTROL, WIN_A_ACT_REQ << plane->index);
+
+ return (0);
+}
+
+static int
+dc_plane_disable(struct drm_plane *drm_plane)
+{
+ struct tegra_plane *plane;
+ struct tegra_crtc *crtc;
+ struct dc_softc *sc;
+ uint32_t val, idx;
+
+ if (drm_plane->crtc == NULL)
+ return (0);
+ plane = container_of(drm_plane, struct tegra_plane, drm_plane);
+ crtc = container_of(drm_plane->crtc, struct tegra_crtc, drm_crtc);
+
+ sc = device_get_softc(crtc->dev);
+ idx = plane->index;
+
+ LOCK(sc);
+
+ WR4(sc, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT << idx);
+
+ val = RD4(sc, DC_WINC_WIN_OPTIONS);
+ val &= ~WIN_ENABLE;
+ WR4(sc, DC_WINC_WIN_OPTIONS, val);
+
+ UNLOCK(sc);
+
+ WR4(sc, DC_CMD_STATE_CONTROL, WIN_A_UPDATE << idx);
+ WR4(sc, DC_CMD_STATE_CONTROL, WIN_A_ACT_REQ << idx);
+
+ return (0);
+}
+
+static void
+dc_plane_destroy(struct drm_plane *plane)
+{
+
+ dc_plane_disable(plane);
+ drm_plane_cleanup(plane);
+ free(plane, DRM_MEM_KMS);
+}
+
+static const struct drm_plane_funcs dc_plane_funcs = {
+ .update_plane = dc_plane_update,
+ .disable_plane = dc_plane_disable,
+ .destroy = dc_plane_destroy,
+};
+
+/* -------------------------------------------------------------------
+ *
+ * CRTC helper functions.
+ *
+ */
+static void
+dc_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+ /* Empty function */
+}
+
+static bool
+dc_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted)
+{
+
+ return (true);
+}
+
+static int
+dc_set_base(struct dc_softc *sc, int x, int y, struct tegra_fb *fb)
+{
+ struct dc_window win;
+ int rv;
+
+ memset(&win, 0, sizeof(win));
+ win.src_x = x;
+ win.src_y = y;
+ win.src_w = fb->drm_fb.width;
+ win.src_h = fb->drm_fb.height;
+ win.dst_x = x;
+ win.dst_y = y;
+ win.dst_w = fb->drm_fb.width;
+ win.dst_h = fb->drm_fb.height;
+
+ rv = dc_parse_drm_format(fb, &win);
+ if (rv != 0) {
+ DRM_WARNING("unsupported pixel format %d\n",
+ fb->drm_fb.pixel_format);
+ return (rv);
+ }
+ dc_setup_window(sc, 0, &win);
+
+ return (0);
+}
+
+static int
+dc_crtc_mode_set(struct drm_crtc *drm_crtc, struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted, int x, int y,
+ struct drm_framebuffer *old_fb)
+{
+ struct dc_softc *sc;
+ struct tegra_crtc *crtc;
+ struct tegra_fb *fb;
+ struct dc_window win;
+ uint32_t div, h_ref_to_sync, v_ref_to_sync;
+ int rv;
+
+ crtc = container_of(drm_crtc, struct tegra_crtc, drm_crtc);
+ sc = device_get_softc(crtc->dev);
+ fb = container_of(drm_crtc->fb, struct tegra_fb, drm_fb);
+
+ h_ref_to_sync = 1;
+ v_ref_to_sync = 1;
+ /* Setup timing */
+ rv = dc_setup_clk(sc, drm_crtc, mode, &div);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot set pixel clock\n");
+ return (rv);
+ }
+
+ /* Timing */
+ WR4(sc, DC_DISP_DISP_TIMING_OPTIONS, 0);
+
+ WR4(sc, DC_DISP_REF_TO_SYNC,
+ (v_ref_to_sync << 16) |
+ h_ref_to_sync);
+
+ WR4(sc, DC_DISP_SYNC_WIDTH,
+ ((mode->vsync_end - mode->vsync_start) << 16) |
+ ((mode->hsync_end - mode->hsync_start) << 0));
+
+ WR4(sc, DC_DISP_BACK_PORCH,
+ ((mode->vtotal - mode->vsync_end) << 16) |
+ ((mode->htotal - mode->hsync_end) << 0));
+
+ WR4(sc, DC_DISP_FRONT_PORCH,
+ ((mode->vsync_start - mode->vdisplay) << 16) |
+ ((mode->hsync_start - mode->hdisplay) << 0));
+
+ WR4(sc, DC_DISP_DISP_ACTIVE,
+ (mode->vdisplay << 16) | mode->hdisplay);
+
+ WR4(sc, DC_DISP_DISP_INTERFACE_CONTROL, DISP_DATA_FORMAT(DF1P1C));
+
+ WR4(sc,DC_DISP_DISP_CLOCK_CONTROL,
+ SHIFT_CLK_DIVIDER(div) | PIXEL_CLK_DIVIDER(PCD1));
+
+ memset(&win, 0, sizeof(win));
+ win.src_x = x;
+ win.src_y = y;
+ win.src_w = mode->hdisplay;
+ win.src_h = mode->vdisplay;
+ win.dst_x = x;
+ win.dst_y = y;
+ win.dst_w = mode->hdisplay;
+ win.dst_h = mode->vdisplay;
+
+ rv = dc_parse_drm_format(fb, &win);
+ if (rv != 0) {
+ DRM_WARNING("unsupported pixel format %d\n",
+ drm_crtc->fb->pixel_format);
+ return (rv);
+ }
+
+ dc_setup_window(sc, 0, &win);
+
+ return (0);
+
+}
+
+static int
+dc_crtc_mode_set_base(struct drm_crtc *drm_crtc, int x, int y,
+ struct drm_framebuffer *old_fb)
+{
+ struct dc_softc *sc;
+ struct tegra_crtc *crtc;
+ struct tegra_fb *fb;
+ int rv;
+
+ crtc = container_of(drm_crtc, struct tegra_crtc, drm_crtc);
+ fb = container_of(drm_crtc->fb, struct tegra_fb, drm_fb);
+ sc = device_get_softc(crtc->dev);
+
+ rv = dc_set_base(sc, x, y, fb);
+
+ /* Commit */
+ WR4(sc, DC_CMD_STATE_CONTROL, GENERAL_UPDATE | WIN_A_UPDATE);
+ WR4(sc, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ | WIN_A_ACT_REQ);
+ return (rv);
+}
+
+static void
+dc_crtc_prepare(struct drm_crtc *drm_crtc)
+{
+
+ struct dc_softc *sc;
+ struct tegra_crtc *crtc;
+ uint32_t val;
+
+ crtc = container_of(drm_crtc, struct tegra_crtc, drm_crtc);
+ sc = device_get_softc(crtc->dev);
+
+ WR4(sc, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL, SYNCPT_CNTRL_NO_STALL);
+ /* XXX allocate syncpoint from host1x */
+ WR4(sc, DC_CMD_CONT_SYNCPT_VSYNC, SYNCPT_VSYNC_ENABLE |
+ (sc->tegra_crtc.nvidia_head == 0 ? SYNCPT_VBLANK0: SYNCPT_VBLANK1));
+
+ WR4(sc, DC_CMD_DISPLAY_POWER_CONTROL,
+ PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
+ PW4_ENABLE | PM0_ENABLE | PM1_ENABLE);
+
+ val = RD4(sc, DC_CMD_DISPLAY_COMMAND);
+ val |= DISPLAY_CTRL_MODE(CTRL_MODE_C_DISPLAY);
+ WR4(sc, DC_CMD_DISPLAY_COMMAND, val);
+
+ WR4(sc, DC_CMD_INT_MASK,
+ WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
+ WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT);
+
+ WR4(sc, DC_CMD_INT_ENABLE,
+ VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
+ WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT);
+}
+
+static void
+dc_crtc_commit(struct drm_crtc *drm_crtc)
+{
+ struct dc_softc *sc;
+ struct tegra_crtc *crtc;
+ uint32_t val;
+
+ crtc = container_of(drm_crtc, struct tegra_crtc, drm_crtc);
+ sc = device_get_softc(crtc->dev);
+
+ WR4(sc, DC_CMD_STATE_CONTROL, GENERAL_UPDATE | WIN_A_UPDATE);
+
+ val = RD4(sc, DC_CMD_INT_MASK);
+ val |= FRAME_END_INT;
+ WR4(sc, DC_CMD_INT_MASK, val);
+
+ val = RD4(sc, DC_CMD_INT_ENABLE);
+ val |= FRAME_END_INT;
+ WR4(sc, DC_CMD_INT_ENABLE, val);
+
+ WR4(sc, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ | WIN_A_ACT_REQ);
+}
+
+static void
+dc_crtc_load_lut(struct drm_crtc *crtc)
+{
+
+ /* empty function */
+}
+
+static const struct drm_crtc_helper_funcs dc_crtc_helper_funcs = {
+ .dpms = dc_crtc_dpms,
+ .mode_fixup = dc_crtc_mode_fixup,
+ .mode_set = dc_crtc_mode_set,
+ .mode_set_base = dc_crtc_mode_set_base,
+ .prepare = dc_crtc_prepare,
+ .commit = dc_crtc_commit,
+ .load_lut = dc_crtc_load_lut,
+};
+
+static int
+drm_crtc_index(struct drm_crtc *crtc)
+{
+ int idx;
+ struct drm_crtc *tmp;
+
+ idx = 0;
+ list_for_each_entry(tmp, &crtc->dev->mode_config.crtc_list, head) {
+ if (tmp == crtc)
+ return (idx);
+ idx++;
+ }
+ panic("Cannot find CRTC");
+}
+
+/* -------------------------------------------------------------------
+ *
+ * Exported functions (mainly vsync related).
+ *
+ * XXX revisit this -> convert to bus methods?
+ */
+int
+tegra_dc_get_pipe(struct drm_crtc *drm_crtc)
+{
+ struct tegra_crtc *crtc;
+
+ crtc = container_of(drm_crtc, struct tegra_crtc, drm_crtc);
+ return (crtc->nvidia_head);
+}
+
+void
+tegra_dc_enable_vblank(struct drm_crtc *drm_crtc)
+{
+ struct dc_softc *sc;
+ struct tegra_crtc *crtc;
+ uint32_t val;
+
+ crtc = container_of(drm_crtc, struct tegra_crtc, drm_crtc);
+ sc = device_get_softc(crtc->dev);
+
+ LOCK(sc);
+ val = RD4(sc, DC_CMD_INT_MASK);
+ val |= VBLANK_INT;
+ WR4(sc, DC_CMD_INT_MASK, val);
+ UNLOCK(sc);
+}
+
+void
+tegra_dc_disable_vblank(struct drm_crtc *drm_crtc)
+{
+ struct dc_softc *sc;
+ struct tegra_crtc *crtc;
+ uint32_t val;
+
+ crtc = container_of(drm_crtc, struct tegra_crtc, drm_crtc);
+ sc = device_get_softc(crtc->dev);
+
+ LOCK(sc);
+ val = RD4(sc, DC_CMD_INT_MASK);
+ val &= ~VBLANK_INT;
+ WR4(sc, DC_CMD_INT_MASK, val);
+ UNLOCK(sc);
+}
+
+static void
+dc_finish_page_flip(struct dc_softc *sc)
+{
+ struct drm_crtc *drm_crtc;
+ struct drm_device *drm;
+ struct tegra_fb *fb;
+ struct tegra_bo *bo;
+ uint32_t base;
+ int idx;
+
+ drm_crtc = &sc->tegra_crtc.drm_crtc;
+ drm = drm_crtc->dev;
+ fb = container_of(drm_crtc->fb, struct tegra_fb, drm_fb);
+
+ mtx_lock(&drm->event_lock);
+
+ if (sc->event == NULL) {
+ mtx_unlock(&drm->event_lock);
+ return;
+ }
+
+ LOCK(sc);
+ /* Read active copy of WINBUF_START_ADDR */
+ WR4(sc, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT);
+ WR4(sc, DC_CMD_STATE_ACCESS, READ_MUX);
+ base = RD4(sc, DC_WINBUF_START_ADDR);
+ WR4(sc, DC_CMD_STATE_ACCESS, 0);
+ UNLOCK(sc);
+
+ /* Is already active */
+ bo = tegra_fb_get_plane(fb, 0);
+ if (base == (bo->pbase + fb->drm_fb.offsets[0])) {
+ idx = drm_crtc_index(drm_crtc);
+ drm_send_vblank_event(drm, idx, sc->event);
+ drm_vblank_put(drm, idx);
+ sc->event = NULL;
+ }
+
+ mtx_unlock(&drm->event_lock);
+}
+
+void
+tegra_dc_cancel_page_flip(struct drm_crtc *drm_crtc, struct drm_file *file)
+{
+ struct dc_softc *sc;
+ struct tegra_crtc *crtc;
+ struct drm_device *drm;
+
+ crtc = container_of(drm_crtc, struct tegra_crtc, drm_crtc);
+ sc = device_get_softc(crtc->dev);
+ drm = drm_crtc->dev;
+ mtx_lock(&drm->event_lock);
+
+ if ((sc->event != NULL) && (sc->event->base.file_priv == file)) {
+ sc->event->base.destroy(&sc->event->base);
+ drm_vblank_put(drm, drm_crtc_index(drm_crtc));
+ sc->event = NULL;
+ }
+ mtx_unlock(&drm->event_lock);
+}
+
+/* -------------------------------------------------------------------
+ *
+ * CRTC functions.
+ *
+ */
+static int
+dc_page_flip(struct drm_crtc *drm_crtc, struct drm_framebuffer *drm_fb,
+ struct drm_pending_vblank_event *event)
+{
+ struct dc_softc *sc;
+ struct tegra_crtc *crtc;
+ struct tegra_fb *fb;
+ struct drm_device *drm;
+
+ crtc = container_of(drm_crtc, struct tegra_crtc, drm_crtc);
+ sc = device_get_softc(crtc->dev);
+ fb = container_of(drm_crtc->fb, struct tegra_fb, drm_fb);
+ drm = drm_crtc->dev;
+
+ if (sc->event != NULL)
+ return (-EBUSY);
+
+ if (event != NULL) {
+ event->pipe = sc->tegra_crtc.nvidia_head;
+ sc->event = event;
+ drm_vblank_get(drm, event->pipe);
+ }
+
+ dc_set_base(sc, drm_crtc->x, drm_crtc->y, fb);
+ drm_crtc->fb = drm_fb;
+
+ /* Commit */
+ WR4(sc, DC_CMD_STATE_CONTROL, GENERAL_UPDATE | WIN_A_UPDATE);
+
+ return (0);
+}
+
+static int
+dc_cursor_set(struct drm_crtc *drm_crtc, struct drm_file *file,
+ uint32_t handle, uint32_t width, uint32_t height)
+{
+
+ struct dc_softc *sc;
+ struct tegra_crtc *crtc;
+ struct drm_gem_object *gem;
+ struct tegra_bo *bo;
+ int i;
+ uint32_t val, *src, *dst;
+
+ crtc = container_of(drm_crtc, struct tegra_crtc, drm_crtc);
+ sc = device_get_softc(crtc->dev);
+
+ if (width != height)
+ return (-EINVAL);
+
+ switch (width) {
+ case 32:
+ val = CURSOR_SIZE(C32x32);
+ break;
+ case 64:
+ val = CURSOR_SIZE(C64x64);
+ break;
+ case 128:
+ val = CURSOR_SIZE(C128x128);
+ break;
+ case 256:
+ val = CURSOR_SIZE(C256x256);
+ break;
+ default:
+ return (-EINVAL);
+ }
+
+ bo = NULL;
+ gem = NULL;
+ if (handle != 0) {
+ gem = drm_gem_object_lookup(drm_crtc->dev, file, handle);
+ if (gem == NULL)
+ return (-ENOENT);
+ bo = container_of(gem, struct tegra_bo, gem_obj);
+ }
+
+ if (sc->cursor_gem != NULL) {
+ drm_gem_object_unreference(sc->cursor_gem);
+ }
+ sc->cursor_gem = gem;
+
+ if (bo != NULL) {
+ /*
+ * Copy cursor into cache and convert it from ARGB to RGBA.
+ * XXXX - this is broken by design - client can write to BO at
+ * any time. We can dedicate other window for cursor or switch
+ * to sw cursor in worst case.
+ */
+ src = (uint32_t *)bo->vbase;
+ dst = (uint32_t *)crtc->cursor_vbase;
+ for (i = 0; i < width * height; i++)
+ dst[i] = (src[i] << 8) | (src[i] >> 24);
+
+ val |= CURSOR_CLIP(CC_DISPLAY);
+ val |= CURSOR_START_ADDR(crtc->cursor_pbase);
+ WR4(sc, DC_DISP_CURSOR_START_ADDR, val);
+
+ val = RD4(sc, DC_DISP_BLEND_CURSOR_CONTROL);
+ val &= ~CURSOR_DST_BLEND_FACTOR_SELECT(~0);
+ val &= ~CURSOR_SRC_BLEND_FACTOR_SELECT(~0);
+ val |= CURSOR_MODE_SELECT;
+ val |= CURSOR_DST_BLEND_FACTOR_SELECT(DST_NEG_K1_TIMES_SRC);
+ val |= CURSOR_SRC_BLEND_FACTOR_SELECT(SRC_BLEND_K1_TIMES_SRC);
+ val |= CURSOR_ALPHA(~0);
+ WR4(sc, DC_DISP_BLEND_CURSOR_CONTROL, val);
+
+ val = RD4(sc, DC_DISP_DISP_WIN_OPTIONS);
+ val |= CURSOR_ENABLE;
+ WR4(sc, DC_DISP_DISP_WIN_OPTIONS, val);
+ } else {
+ val = RD4(sc, DC_DISP_DISP_WIN_OPTIONS);
+ val &= ~CURSOR_ENABLE;
+ WR4(sc, DC_DISP_DISP_WIN_OPTIONS, val);
+ }
+
+ /* XXX This fixes cursor underflow issues, but why ? */
+ WR4(sc, DC_DISP_CURSOR_UNDERFLOW_CTRL, CURSOR_UFLOW_CYA);
+
+ WR4(sc, DC_CMD_STATE_CONTROL, GENERAL_UPDATE | CURSOR_UPDATE );
+ WR4(sc, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ | CURSOR_ACT_REQ);
+ return (0);
+}
+
+static int
+dc_cursor_move(struct drm_crtc *drm_crtc, int x, int y)
+{
+ struct dc_softc *sc;
+ struct tegra_crtc *crtc;
+
+ crtc = container_of(drm_crtc, struct tegra_crtc, drm_crtc);
+ sc = device_get_softc(crtc->dev);
+ WR4(sc, DC_DISP_CURSOR_POSITION, CURSOR_POSITION(x, y));
+
+ WR4(sc, DC_CMD_STATE_CONTROL, CURSOR_UPDATE);
+ WR4(sc, DC_CMD_STATE_CONTROL, CURSOR_ACT_REQ);
+
+ return (0);
+}
+
+static void
+dc_destroy(struct drm_crtc *crtc)
+{
+
+ drm_crtc_cleanup(crtc);
+ memset(crtc, 0, sizeof(*crtc));
+}
+
+static const struct drm_crtc_funcs dc_crtc_funcs = {
+ .page_flip = dc_page_flip,
+ .cursor_set = dc_cursor_set,
+ .cursor_move = dc_cursor_move,
+ .set_config = drm_crtc_helper_set_config,
+ .destroy = dc_destroy,
+};
+
+/* -------------------------------------------------------------------
+ *
+ * Bus and infrastructure.
+ *
+ */
+static int
+dc_init_planes(struct dc_softc *sc, struct tegra_drm *drm)
+{
+ int i, rv;
+ struct tegra_plane *plane;
+
+ rv = 0;
+ for (i = 0; i < DC_MAX_PLANES; i++) {
+ plane = malloc(sizeof(*plane), DRM_MEM_KMS, M_WAITOK | M_ZERO);
+ plane->index = i + 1;
+ rv = drm_plane_init(&drm->drm_dev, &plane->drm_plane,
+ 1 << sc->tegra_crtc.nvidia_head, &dc_plane_funcs,
+ dc_plane_formats, nitems(dc_plane_formats), false);
+ if (rv != 0) {
+ free(plane, DRM_MEM_KMS);
+ return (rv);
+ }
+ }
+ return 0;
+}
+
+static void
+dc_display_enable(device_t dev, bool enable)
+{
+ struct dc_softc *sc;
+ uint32_t val;
+
+ sc = device_get_softc(dev);
+
+ /* Set display mode */
+ val = enable ? CTRL_MODE_C_DISPLAY: CTRL_MODE_STOP;
+ WR4(sc, DC_CMD_DISPLAY_COMMAND, DISPLAY_CTRL_MODE(val));
+
+ /* and commit it*/
+ WR4(sc, DC_CMD_STATE_CONTROL, GENERAL_UPDATE);
+ WR4(sc, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ);
+}
+
+static void
+dc_hdmi_enable(device_t dev, bool enable)
+{
+ struct dc_softc *sc;
+ uint32_t val;
+
+ sc = device_get_softc(dev);
+
+ val = RD4(sc, DC_DISP_DISP_WIN_OPTIONS);
+ if (enable)
+ val |= HDMI_ENABLE;
+ else
+ val &= ~HDMI_ENABLE;
+ WR4(sc, DC_DISP_DISP_WIN_OPTIONS, val);
+
+}
+
+static void
+dc_setup_timing(device_t dev, int h_pulse_start)
+{
+ struct dc_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ /* Setup display timing */
+ WR4(sc, DC_DISP_DISP_TIMING_OPTIONS, VSYNC_H_POSITION(1));
+ WR4(sc, DC_DISP_DISP_COLOR_CONTROL,
+ DITHER_CONTROL(DITHER_DISABLE) | BASE_COLOR_SIZE(SIZE_BASE888));
+
+ WR4(sc, DC_DISP_DISP_SIGNAL_OPTIONS0, H_PULSE2_ENABLE);
+ WR4(sc, DC_DISP_H_PULSE2_CONTROL,
+ PULSE_CONTROL_QUAL(QUAL_VACTIVE) | PULSE_CONTROL_LAST(LAST_END_A));
+
+ WR4(sc, DC_DISP_H_PULSE2_POSITION_A,
+ PULSE_START(h_pulse_start) | PULSE_END(h_pulse_start + 8));
+}
+
+static void
+dc_intr(void *arg)
+{
+ struct dc_softc *sc;
+ uint32_t status;
+
+ sc = arg;
+
+ /* Confirm interrupt */
+ status = RD4(sc, DC_CMD_INT_STATUS);
+ WR4(sc, DC_CMD_INT_STATUS, status);
+ if (status & VBLANK_INT) {
+ drm_handle_vblank(sc->tegra_crtc.drm_crtc.dev,
+ sc->tegra_crtc.nvidia_head);
+ dc_finish_page_flip(sc);
+ }
+}
+
+static int
+dc_init_client(device_t dev, device_t host1x, struct tegra_drm *drm)
+{
+ struct dc_softc *sc;
+ int rv;
+
+ sc = device_get_softc(dev);
+
+ if (drm->pitch_align < sc->pitch_align)
+ drm->pitch_align = sc->pitch_align;
+
+ drm_crtc_init(&drm->drm_dev, &sc->tegra_crtc.drm_crtc, &dc_crtc_funcs);
+ drm_mode_crtc_set_gamma_size(&sc->tegra_crtc.drm_crtc, 256);
+ drm_crtc_helper_add(&sc->tegra_crtc.drm_crtc, &dc_crtc_helper_funcs);
+
+ rv = dc_init_planes(sc, drm);
+ if (rv!= 0){
+ device_printf(dev, "Cannot init planes\n");
+ return (rv);
+ }
+
+ WR4(sc, DC_CMD_INT_TYPE,
+ WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
+ WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT);
+
+ WR4(sc, DC_CMD_INT_POLARITY,
+ WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
+ WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT);
+
+ WR4(sc, DC_CMD_INT_ENABLE, 0);
+ WR4(sc, DC_CMD_INT_MASK, 0);
+
+ rv = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
+ NULL, dc_intr, sc, &sc->irq_ih);
+ if (rv != 0) {
+ device_printf(dev, "Cannot register interrupt handler\n");
+ return (rv);
+ }
+
+ /* allocate memory for cursor cache */
+ sc->tegra_crtc.cursor_vbase = kmem_alloc_contig(256 * 256 * 4,
+ M_WAITOK | M_ZERO, 0, -1UL, PAGE_SIZE, 0,
+ VM_MEMATTR_WRITE_COMBINING);
+ sc->tegra_crtc.cursor_pbase = vtophys(sc->tegra_crtc.cursor_vbase);
+ return (0);
+}
+
+static int
+dc_exit_client(device_t dev, device_t host1x, struct tegra_drm *drm)
+{
+ struct dc_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ if (sc->irq_ih != NULL)
+ bus_teardown_intr(dev, sc->irq_res, sc->irq_ih);
+ sc->irq_ih = NULL;
+
+ return (0);
+}
+
+static int
+get_fdt_resources(struct dc_softc *sc, phandle_t node)
+{
+ int rv;
+
+ rv = hwreset_get_by_ofw_name(sc->dev, 0, "dc", &sc->hwreset_dc);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot get 'dc' reset\n");
+ return (rv);
+ }
+ rv = clk_get_by_ofw_name(sc->dev, 0, "parent", &sc->clk_parent);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot get 'parent' clock\n");
+ return (rv);
+ }
+ rv = clk_get_by_ofw_name(sc->dev, 0, "dc", &sc->clk_dc);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot get 'dc' clock\n");
+ return (rv);
+ }
+
+ rv = OF_getencprop(node, "nvidia,head", &sc->tegra_crtc.nvidia_head,
+ sizeof(sc->tegra_crtc.nvidia_head));
+ if (rv <= 0) {
+ device_printf(sc->dev,
+ "Cannot get 'nvidia,head' property\n");
+ return (rv);
+ }
+ return (0);
+}
+
+static int
+enable_fdt_resources(struct dc_softc *sc)
+{
+ int id, rv;
+
+ rv = clk_set_parent_by_clk(sc->clk_dc, sc->clk_parent);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot set parent for 'dc' clock\n");
+ return (rv);
+ }
+
+ id = (sc->tegra_crtc.nvidia_head == 0) ?
+ TEGRA_POWERGATE_DIS: TEGRA_POWERGATE_DISB;
+ rv = tegra_powergate_sequence_power_up(id, sc->clk_dc, sc->hwreset_dc);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot enable 'DIS' powergate\n");
+ return (rv);
+ }
+
+ return (0);
+}
+
+static int
+dc_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
+ return (ENXIO);
+
+ device_set_desc(dev, "Tegra Display Controller");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+dc_attach(device_t dev)
+{
+ struct dc_softc *sc;
+ phandle_t node;
+ int rid, rv;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ sc->tegra_crtc.dev = dev;
+
+ node = ofw_bus_get_node(sc->dev);
+ LOCK_INIT(sc);
+
+ rid = 0;
+ sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->mem_res == NULL) {
+ device_printf(dev, "Cannot allocate memory resources\n");
+ goto fail;
+ }
+
+ rid = 0;
+ sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
+ if (sc->irq_res == NULL) {
+ device_printf(dev, "Cannot allocate IRQ resources\n");
+ goto fail;
+ }
+
+ rv = get_fdt_resources(sc, node);
+ if (rv != 0) {
+ device_printf(dev, "Cannot parse FDT resources\n");
+ goto fail;
+ }
+ rv = enable_fdt_resources(sc);
+ if (rv != 0) {
+ device_printf(dev, "Cannot enable FDT resources\n");
+ goto fail;
+ }
+
+ /*
+ * Tegra124
+ * - 64 for RGB modes
+ * - 128 for YUV planar modes
+ * - 256 for block linear modes
+ */
+ sc->pitch_align = 256;
+
+ rv = TEGRA_DRM_REGISTER_CLIENT(device_get_parent(sc->dev), sc->dev);
+ if (rv != 0) {
+ device_printf(dev, "Cannot register DRM device\n");
+ goto fail;
+ }
+
+ return (bus_generic_attach(dev));
+
+fail:
+ TEGRA_DRM_DEREGISTER_CLIENT(device_get_parent(sc->dev), sc->dev);
+ if (sc->irq_ih != NULL)
+ bus_teardown_intr(dev, sc->irq_res, sc->irq_ih);
+ if (sc->clk_parent != NULL)
+ clk_release(sc->clk_parent);
+ if (sc->clk_dc != NULL)
+ clk_release(sc->clk_dc);
+ if (sc->hwreset_dc != NULL)
+ hwreset_release(sc->hwreset_dc);
+ if (sc->irq_res != NULL)
+ bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq_res);
+ if (sc->mem_res != NULL)
+ bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->mem_res);
+ LOCK_DESTROY(sc);
+
+ return (ENXIO);
+}
+
+static int
+dc_detach(device_t dev)
+{
+ struct dc_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ TEGRA_DRM_DEREGISTER_CLIENT(device_get_parent(sc->dev), sc->dev);
+
+ if (sc->irq_ih != NULL)
+ bus_teardown_intr(dev, sc->irq_res, sc->irq_ih);
+ if (sc->clk_parent != NULL)
+ clk_release(sc->clk_parent);
+ if (sc->clk_dc != NULL)
+ clk_release(sc->clk_dc);
+ if (sc->hwreset_dc != NULL)
+ hwreset_release(sc->hwreset_dc);
+ if (sc->irq_res != NULL)
+ bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq_res);
+ if (sc->mem_res != NULL)
+ bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->mem_res);
+ LOCK_DESTROY(sc);
+
+ return (bus_generic_detach(dev));
+}
+
+static device_method_t tegra_dc_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, dc_probe),
+ DEVMETHOD(device_attach, dc_attach),
+ DEVMETHOD(device_detach, dc_detach),
+
+ /* tegra drm interface */
+ DEVMETHOD(tegra_drm_init_client, dc_init_client),
+ DEVMETHOD(tegra_drm_exit_client, dc_exit_client),
+
+ /* tegra dc interface */
+ DEVMETHOD(tegra_dc_display_enable, dc_display_enable),
+ DEVMETHOD(tegra_dc_hdmi_enable, dc_hdmi_enable),
+ DEVMETHOD(tegra_dc_setup_timing, dc_setup_timing),
+
+ DEVMETHOD_END
+};
+
+static devclass_t tegra_dc_devclass;
+DEFINE_CLASS_0(tegra_dc, tegra_dc_driver, tegra_dc_methods,
+ sizeof(struct dc_softc));
+DRIVER_MODULE(tegra_dc, host1x, tegra_dc_driver, tegra_dc_devclass, NULL, NULL);
diff --git a/sys/arm/nvidia/drm2/tegra_dc_if.m b/sys/arm/nvidia/drm2/tegra_dc_if.m
new file mode 100644
index 000000000000..5b3bd1437767
--- /dev/null
+++ b/sys/arm/nvidia/drm2/tegra_dc_if.m
@@ -0,0 +1,57 @@
+#-
+# Copyright (c) 2015 Michal Meloun
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+#
+
+#include <machine/bus.h>
+
+INTERFACE tegra_dc;
+
+
+METHOD void write_4{
+ device_t dev;
+ bus_size_t offset;
+ uint32_t val;
+};
+METHOD uint32_t read_4{
+ device_t dev;
+ bus_size_t offset;
+};
+
+METHOD void display_enable{
+ device_t dev;
+ bool enable;
+};
+
+METHOD void hdmi_enable{
+ device_t dev;
+ bool enable;
+};
+
+METHOD void setup_timing{
+ device_t dev;
+ int h_pulse_start;
+};
diff --git a/sys/arm/nvidia/drm2/tegra_dc_reg.h b/sys/arm/nvidia/drm2/tegra_dc_reg.h
new file mode 100644
index 000000000000..377f3e210681
--- /dev/null
+++ b/sys/arm/nvidia/drm2/tegra_dc_reg.h
@@ -0,0 +1,398 @@
+/*-
+ * Copyright 1992-2015 Michal Meloun
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _TEGRA_DC_REG_H_
+#define _TEGRA_DC_REG_H_
+
+/*
+ * !!! WARNING !!!
+ * Tegra manual uses registers index (and not register addreses).
+ * We follow the TRM notation and index is converted to offset in
+ * WR4 / RD4 macros
+ */
+
+/* --------------------------- DC CMD -------------------------------------- */
+#define DC_CMD_GENERAL_INCR_SYNCPT 0x000
+#define DC_CMD_GENERAL_INCR_SYNCPT_CNTRL 0x001
+#define SYNCPT_CNTRL_NO_STALL (1 << 8)
+#define SYNCPT_CNTRL_SOFT_RESET (1 << 0)
+
+#define DC_CMD_GENERAL_INCR_SYNCPT_ERROR 0x002
+#define DC_CMD_WIN_A_INCR_SYNCPT 0x008
+#define DC_CMD_WIN_A_INCR_SYNCPT_CNTRL 0x009
+#define DC_CMD_WIN_A_INCR_SYNCPT_ERROR 0x00a
+#define DC_CMD_WIN_B_INCR_SYNCPT 0x010
+#define DC_CMD_WIN_B_INCR_SYNCPT_CNTRL 0x011
+#define DC_CMD_WIN_B_INCR_SYNCPT_ERROR 0x012
+#define DC_CMD_WIN_C_INCR_SYNCPT 0x018
+#define DC_CMD_WIN_C_INCR_SYNCPT_CNTRL 0x019
+#define DC_CMD_WIN_C_INCR_SYNCPT_ERROR 0x01a
+#define DC_CMD_CONT_SYNCPT_VSYNC 0x028
+#define SYNCPT_VSYNC_ENABLE (1 << 8)
+
+#define DC_CMD_CTXSW 0x030
+#define DC_CMD_DISPLAY_COMMAND_OPTION0 0x031
+#define DC_CMD_DISPLAY_COMMAND 0x032
+#define DISPLAY_CTRL_MODE(x) ((x) << 5)
+#define CTRL_MODE_STOP 0
+#define CTRL_MODE_C_DISPLAY 1
+#define CTRL_MODE_NC_DISPLAY 2
+
+#define DC_CMD_SIGNAL_RAISE 0x033
+#define DC_CMD_DISPLAY_POWER_CONTROL 0x036
+#define PM1_ENABLE (1 << 18)
+#define PM0_ENABLE (1 << 16)
+#define PW4_ENABLE (1 << 8)
+#define PW3_ENABLE (1 << 6)
+#define PW2_ENABLE (1 << 4)
+#define PW1_ENABLE (1 << 2)
+#define PW0_ENABLE (1 << 0)
+
+#define DC_CMD_INT_STATUS 0x037
+#define DC_CMD_INT_MASK 0x038
+#define DC_CMD_INT_ENABLE 0x039
+#define DC_CMD_INT_TYPE 0x03a
+#define DC_CMD_INT_POLARITY 0x03b
+#define WIN_T_UF_INT (1 << 25)
+#define WIN_D_UF_INT (1 << 24)
+#define HC_UF_INT (1 << 23)
+#define CMU_LUT_CONFLICT_INT (1 << 22)
+#define WIN_C_OF_INT (1 << 16)
+#define WIN_B_OF_INT (1 << 15)
+#define WIN_A_OF_INT (1 << 14)
+#define SSF_INT (1 << 13)
+#define MSF_INT (1 << 12)
+#define WIN_C_UF_INT (1 << 10)
+#define WIN_B_UF_INT (1 << 9)
+#define WIN_A_UF_INT (1 << 8)
+#define SPI_BUSY_INT (1 << 6)
+#define V_PULSE2_INT (1 << 5)
+#define V_PULSE3_INT (1 << 4)
+#define HBLANK_INT (1 << 3)
+#define VBLANK_INT (1 << 2)
+#define FRAME_END_INT (1 << 1)
+
+#define DC_CMD_STATE_ACCESS 0x040
+#define WRITE_MUX (1 << 2)
+#define READ_MUX (1 << 0)
+
+#define DC_CMD_STATE_CONTROL 0x041
+#define NC_HOST_TRIG (1 << 24)
+#define CURSOR_UPDATE (1 << 15)
+#define WIN_C_UPDATE (1 << 11)
+#define WIN_B_UPDATE (1 << 10)
+#define WIN_A_UPDATE (1 << 9)
+#define WIN_UPDATE(x) (1 << (9 + (x)))
+#define GENERAL_UPDATE (1 << 8)
+#define CURSOR_ACT_REQ (1 << 7)
+#define WIN_D_ACT_REQ (1 << 4)
+#define WIN_C_ACT_REQ (1 << 3)
+#define WIN_B_ACT_REQ (1 << 2)
+#define WIN_A_ACT_REQ (1 << 1)
+#define WIN_ACT_REQ(x) (1 << (1 + (x)))
+#define GENERAL_ACT_REQ (1 << 0)
+
+#define DC_CMD_DISPLAY_WINDOW_HEADER 0x042
+#define WINDOW_D_SELECT (1 << 7)
+#define WINDOW_C_SELECT (1 << 6)
+#define WINDOW_B_SELECT (1 << 5)
+#define WINDOW_A_SELECT (1 << 4)
+#define WINDOW_SELECT(x) (1 << (4 + (x)))
+
+#define DC_CMD_REG_ACT_CONTROL 0x043
+#define DC_CMD_WIN_D_INCR_SYNCPT 0x04c
+#define DC_CMD_WIN_D_INCR_SYNCPT_CNTRL 0x04d
+#define DC_CMD_WIN_D_INCR_SYNCPT_ERROR 0x04e
+
+/* ---------------------------- DC COM ------------------------------------- */
+
+/* --------------------------- DC DISP ------------------------------------- */
+
+#define DC_DISP_DISP_SIGNAL_OPTIONS0 0x400
+#define M1_ENABLE (1 << 26)
+#define M0_ENABLE (1 << 24)
+#define V_PULSE2_ENABLE (1 << 18)
+#define V_PULSE1_ENABLE (1 << 16)
+#define V_PULSE0_ENABLE (1 << 14)
+#define H_PULSE2_ENABLE (1 << 12)
+#define H_PULSE1_ENABLE (1 << 10)
+#define H_PULSE0_ENABLE (1 << 8)
+
+#define DC_DISP_DISP_SIGNAL_OPTIONS1 0x401
+
+#define DC_DISP_DISP_WIN_OPTIONS 0x402
+#define HDMI_ENABLE (1 << 30)
+#define DSI_ENABLE (1 << 29)
+#define SOR1_TIMING_CYA (1 << 27)
+#define SOR1_ENABLE (1 << 26)
+#define SOR_ENABLE (1 << 25)
+#define CURSOR_ENABLE (1 << 16)
+
+#define DC_DISP_DISP_TIMING_OPTIONS 0x405
+#define VSYNC_H_POSITION(x) (((x) & 0xfff) << 0)
+
+#define DC_DISP_REF_TO_SYNC 0x406
+#define DC_DISP_SYNC_WIDTH 0x407
+#define DC_DISP_BACK_PORCH 0x408
+#define DC_DISP_DISP_ACTIVE 0x409
+#define DC_DISP_FRONT_PORCH 0x40a
+#define DC_DISP_H_PULSE0_CONTROL 0x40b
+#define DC_DISP_H_PULSE0_POSITION_A 0x40c
+#define DC_DISP_H_PULSE0_POSITION_B 0x40d
+#define DC_DISP_H_PULSE0_POSITION_C 0x40e
+#define DC_DISP_H_PULSE0_POSITION_D 0x40f
+#define DC_DISP_H_PULSE1_CONTROL 0x410
+#define DC_DISP_H_PULSE1_POSITION_A 0x411
+#define DC_DISP_H_PULSE1_POSITION_B 0x412
+#define DC_DISP_H_PULSE1_POSITION_C 0x413
+#define DC_DISP_H_PULSE1_POSITION_D 0x414
+#define DC_DISP_H_PULSE2_CONTROL 0x415
+#define DC_DISP_H_PULSE2_POSITION_A 0x416
+#define DC_DISP_H_PULSE2_POSITION_B 0x417
+#define DC_DISP_H_PULSE2_POSITION_C 0x418
+#define DC_DISP_H_PULSE2_POSITION_D 0x419
+#define DC_DISP_V_PULSE0_CONTROL 0x41a
+#define DC_DISP_V_PULSE0_POSITION_A 0x41b
+#define DC_DISP_V_PULSE0_POSITION_B 0x41c
+#define DC_DISP_V_PULSE0_POSITION_C 0x41d
+#define DC_DISP_V_PULSE1_CONTROL 0x41e
+#define DC_DISP_V_PULSE1_POSITION_A 0x41f
+#define DC_DISP_V_PULSE1_POSITION_B 0x420
+#define DC_DISP_V_PULSE1_POSITION_C 0x421
+#define DC_DISP_V_PULSE2_CONTROL 0x422
+#define DC_DISP_V_PULSE2_POSITION_A 0x423
+#define DC_DISP_V_PULSE3_CONTROL 0x424
+#define PULSE_CONTROL_LAST(x) (((x) & 0x7f) << 8)
+#define LAST_START_A 0
+#define LAST_END_A 1
+#define LAST_START_B 2
+#define LAST_END_B 3
+#define LAST_START_C 4
+#define LAST_END_C 5
+#define LAST_START_D 6
+#define LAST_END_D 7
+#define PULSE_CONTROL_QUAL(x) (((x) & 0x3) << 8)
+#define QUAL_ALWAYS 0
+#define QUAL_VACTIVE 2
+#define QUAL_VACTIVE1 3
+#define PULSE_POLARITY (1 << 4)
+#define PULSE_MODE (1 << 3)
+
+#define DC_DISP_V_PULSE3_POSITION_A 0x425
+#define PULSE_END(x) (((x) & 0xfff) << 16)
+#define PULSE_START(x) (((x) & 0xfff) << 0)
+
+#define DC_DISP_DISP_CLOCK_CONTROL 0x42e
+#define PIXEL_CLK_DIVIDER(x) (((x) & 0xf) << 8)
+#define PCD1 0
+#define PCD1H 1
+#define PCD2 2
+#define PCD3 3
+#define PCD4 4
+#define PCD6 5
+#define PCD8 6
+#define PCD9 7
+#define PCD12 8
+#define PCD16 9
+#define PCD18 10
+#define PCD24 11
+#define PCD13 12
+#define SHIFT_CLK_DIVIDER(x) ((x) & 0xff)
+
+#define DC_DISP_DISP_INTERFACE_CONTROL 0x42f
+#define DISP_ORDER_BLUE_RED ( 1 << 9)
+#define DISP_ALIGNMENT_LSB ( 1 << 8)
+#define DISP_DATA_FORMAT(x) (((x) & 0xf) << 8)
+#define DF1P1C 0
+#define DF1P2C24B 1
+#define DF1P2C18B 2
+#define DF1P2C16B 3
+#define DF1S 4
+#define DF2S 5
+#define DF3S 6
+#define DFSPI 7
+#define DF1P3C24B 8
+#define DF2P1C18B 9
+#define DFDUAL1P1C18B 10
+
+#define DC_DISP_DISP_COLOR_CONTROL 0x430
+#define NON_BASE_COLOR (1 << 18)
+#define BLANK_COLOR (1 << 17)
+#define DISP_COLOR_SWAP (1 << 16)
+#define ORD_DITHER_ROTATION(x) (((x) & 0x3) << 12)
+#define DITHER_CONTROL(x) (((x) & 0x3) << 8)
+#define DITHER_DISABLE 0
+#define DITHER_ORDERED 2
+#define DITHER_TEMPORAL 3
+#define BASE_COLOR_SIZE(x) (((x) & 0xF) << 0)
+#define SIZE_BASE666 0
+#define SIZE_BASE111 1
+#define SIZE_BASE222 2
+#define SIZE_BASE333 3
+#define SIZE_BASE444 4
+#define SIZE_BASE555 5
+#define SIZE_BASE565 6
+#define SIZE_BASE332 7
+#define SIZE_BASE888 8
+
+#define DC_DISP_CURSOR_START_ADDR 0x43e
+#define CURSOR_CLIP(x) (((x) & 0x3) << 28)
+#define CC_DISPLAY 0
+#define CC_WA 1
+#define CC_WB 2
+#define CC_WC 3
+#define CURSOR_SIZE(x) (((x) & 0x3) << 24)
+#define C32x32 0
+#define C64x64 1
+#define C128x128 2
+#define C256x256 3
+#define CURSOR_START_ADDR(x) (((x) >> 10) & 0x3FFFFF)
+
+#define DC_DISP_CURSOR_POSITION 0x440
+#define CURSOR_POSITION(h, v) ((((h) & 0x3fff) << 0) | \
+ (((v) & 0x3fff) << 16))
+#define DC_DISP_CURSOR_UNDERFLOW_CTRL 0x4eb
+#define DC_DISP_BLEND_CURSOR_CONTROL 0x4f1
+#define CURSOR_MODE_SELECT (1 << 24)
+#define CURSOR_DST_BLEND_FACTOR_SELECT(x) (((x) & 0x3) << 16)
+#define DST_BLEND_ZERO 0
+#define DST_BLEND_K1 1
+#define DST_NEG_K1_TIMES_SRC 2
+#define CURSOR_SRC_BLEND_FACTOR_SELECT(x) (((x) & 0x3) << 8)
+#define SRC_BLEND_K1 0
+#define SRC_BLEND_K1_TIMES_SRC 1
+#define CURSOR_ALPHA(x) (((x) & 0xFF) << 0)
+
+#define DC_DISP_CURSOR_UFLOW_DBG_PIXEL 0x4f3
+#define CURSOR_UFLOW_CYA (1 << 7)
+#define CURSOR_UFLOW_CTRL_DBG_MODE (1 << 0)
+/* --------------------------- DC WIN ------------------------------------- */
+
+#define DC_WINC_COLOR_PALETTE 0x500
+#define DC_WINC_CSC_YOF 0x611
+#define DC_WINC_CSC_KYRGB 0x612
+#define DC_WINC_CSC_KUR 0x613
+#define DC_WINC_CSC_KVR 0x614
+#define DC_WINC_CSC_KUG 0x615
+#define DC_WINC_CSC_KVG 0x616
+#define DC_WINC_CSC_KUB 0x617
+#define DC_WINC_CSC_KVB 0x618
+
+#define DC_WINC_WIN_OPTIONS 0x700
+#define H_FILTER_MODE (1U << 31)
+#define WIN_ENABLE (1 << 30)
+#define INTERLACE_ENABLE (1 << 23)
+#define YUV_RANGE_EXPAND (1 << 22)
+#define DV_ENABLE (1 << 20)
+#define CSC_ENABLE (1 << 18)
+#define CP_ENABLE (1 << 16)
+#define V_FILTER_UV_ALIGN (1 << 14)
+#define V_FILTER_OPTIMIZE (1 << 12)
+#define V_FILTER_ENABLE (1 << 10)
+#define H_FILTER_ENABLE (1 << 8)
+#define COLOR_EXPAND (1 << 6)
+#define SCAN_COLUMN (1 << 4)
+#define V_DIRECTION (1 << 2)
+#define H_DIRECTION (1 << 0)
+
+#define DC_WIN_BYTE_SWAP 0x701
+#define BYTE_SWAP(x) (((x) & 0x7) << 0)
+#define NOSWAP 0
+#define SWAP2 1
+#define SWAP4 2
+#define SWAP4HW 3
+#define SWAP02 4
+#define SWAPLEFT 5
+
+#define DC_WIN_COLOR_DEPTH 0x703
+#define WIN_COLOR_DEPTH_P8 3
+#define WIN_COLOR_DEPTH_B4G4R4A4 4
+#define WIN_COLOR_DEPTH_B5G5R5A 5
+#define WIN_COLOR_DEPTH_B5G6R5 6
+#define WIN_COLOR_DEPTH_AB5G5R5 7
+#define WIN_COLOR_DEPTH_B8G8R8A8 12
+#define WIN_COLOR_DEPTH_R8G8B8A8 13
+#define WIN_COLOR_DEPTH_YCbCr422 16
+#define WIN_COLOR_DEPTH_YUV422 17
+#define WIN_COLOR_DEPTH_YCbCr420P 18
+#define WIN_COLOR_DEPTH_YUV420P 19
+#define WIN_COLOR_DEPTH_YCbCr422P 20
+#define WIN_COLOR_DEPTH_YUV422P 21
+#define WIN_COLOR_DEPTH_YCbCr422R 22
+#define WIN_COLOR_DEPTH_YUV422R 23
+#define WIN_COLOR_DEPTH_YCbCr422RA 24
+#define WIN_COLOR_DEPTH_YUV422RA 25
+
+#define DC_WIN_POSITION 0x704
+#define WIN_POSITION(h, v) ((((h) & 0x1fff) << 0) | \
+ (((v) & 0x1fff) << 16))
+
+#define DC_WIN_SIZE 0x705
+#define WIN_SIZE(h, v) ((((h) & 0x1fff) << 0) | \
+ (((v) & 0x1fff) << 16))
+
+#define DC_WIN_PRESCALED_SIZE 0x706
+#define WIN_PRESCALED_SIZE(h, v) ((((h) & 0x7fff) << 0) | \
+ (((v) & 0x1fff) << 16))
+
+#define DC_WIN_H_INITIAL_DDA 0x707
+#define DC_WIN_V_INITIAL_DDA 0x708
+#define DC_WIN_DDA_INCREMENT 0x709
+#define WIN_DDA_INCREMENT(h, v) ((((h) & 0xffff) << 0) | \
+ (((v) & 0xffff) << 16))
+#define DC_WIN_LINE_STRIDE 0x70a
+
+/* -------------------------- DC WINBUF ------------------------------------ */
+
+#define DC_WINBUF_START_ADDR 0x800
+#define DC_WINBUF_START_ADDR_NS 0x801
+#define DC_WINBUF_START_ADDR_U 0x802
+#define DC_WINBUF_START_ADDR_U_NS 0x803
+#define DC_WINBUF_START_ADDR_V 0x804
+#define DC_WINBUF_START_ADDR_V_NS 0x805
+#define DC_WINBUF_ADDR_H_OFFSET 0x806
+#define DC_WINBUF_ADDR_H_OFFSET_NS 0x807
+#define DC_WINBUF_ADDR_V_OFFSET 0x808
+#define DC_WINBUF_ADDR_V_OFFSET_NS 0x809
+#define DC_WINBUF_UFLOW_STATUS 0x80a
+#define DC_WINBUF_SURFACE_KIND 0x80b
+#define SURFACE_KIND_BLOCK_HEIGHT(x) (((x) & 0x7) << 4)
+#define SURFACE_KIND_PITCH 0
+#define SURFACE_KIND_TILED 1
+#define SURFACE_KIND_BL_16B2 2
+#define DC_WINBUF_SURFACE_WEIGHT 0x80c
+#define DC_WINBUF_START_ADDR_HI 0x80d
+#define DC_WINBUF_START_ADDR_HI_NS 0x80e
+#define DC_WINBUF_START_ADDR_U_HI 0x80f
+#define DC_WINBUF_START_ADDR_U_HI_NS 0x810
+#define DC_WINBUF_START_ADDR_V_HI 0x811
+#define DC_WINBUF_START_ADDR_V_HI_NS 0x812
+#define DC_WINBUF_UFLOW_CTRL 0x824
+#define UFLOW_CTR_ENABLE (1 << 0)
+#define DC_WINBUF_UFLOW_DBG_PIXEL 0x825
+
+#endif /* _TEGRA_DC_REG_H_ */
diff --git a/sys/arm/nvidia/drm2/tegra_drm.h b/sys/arm/nvidia/drm2/tegra_drm.h
new file mode 100644
index 000000000000..ada4f4434e65
--- /dev/null
+++ b/sys/arm/nvidia/drm2/tegra_drm.h
@@ -0,0 +1,124 @@
+/*-
+ * Copyright 1992-2015 Michal Meloun
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _TEGRA_DRM_H_
+#define _TEGRA_DRM_H_
+
+#include <dev/gpio/gpiobusvar.h>
+
+struct tegra_bo {
+ struct drm_gem_object gem_obj;
+ /* mapped memory buffer */
+ vm_paddr_t pbase;
+ vm_offset_t vbase;
+ size_t npages;
+ vm_page_t *m;
+ vm_object_t cdev_pager;
+};
+
+struct tegra_plane {
+ struct drm_plane drm_plane;
+ int index; /* Window index */
+};
+
+struct tegra_fb {
+ struct drm_framebuffer drm_fb;
+ struct drm_fb_helper fb_helper;
+ struct tegra_bo **planes; /* Attached planes */
+ int nplanes;
+
+ /* Surface and display geometry */
+ bool block_linear; /* Surface_kind */
+ uint32_t block_height;
+ int rotation; /* In degrees */
+ bool flip_x; /* Inverted X-axis */
+ bool flip_y; /* Inverted Y-axis */
+};
+
+struct tegra_crtc {
+ struct drm_crtc drm_crtc;
+ device_t dev;
+ int nvidia_head;
+ vm_paddr_t cursor_pbase; /* Cursor buffer */
+ vm_offset_t cursor_vbase;
+};
+
+struct tegra_drm_encoder {
+ device_t dev;
+
+ void *panel; /* XXX For LVDS panel */
+ device_t ddc;
+ struct edid *edid;
+
+ gpio_pin_t gpio_hpd;
+
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+ int (*setup_clock)(struct tegra_drm_encoder *output,
+ clk_t clk, uint64_t pclk);
+};
+
+struct tegra_drm {
+ struct drm_device drm_dev;
+ struct tegra_fb *fb; /* Prime framebuffer */
+ int pitch_align;
+};
+
+/* tegra_drm_subr.c */
+int tegra_drm_encoder_attach(struct tegra_drm_encoder *output, phandle_t node);
+int tegra_drm_encoder_init(struct tegra_drm_encoder *output,
+ struct tegra_drm *drm);
+int tegra_drm_encoder_exit(struct tegra_drm_encoder *output,
+ struct tegra_drm *drm);
+enum drm_connector_status tegra_drm_connector_detect(
+ struct drm_connector *connector, bool force);
+int tegra_drm_connector_get_modes(struct drm_connector *connector);
+struct drm_encoder *tegra_drm_connector_best_encoder(
+ struct drm_connector *connector);
+
+/* tegra_dc.c */
+void tegra_dc_cancel_page_flip(struct drm_crtc *drm_crtc,
+ struct drm_file *file);
+void tegra_dc_enable_vblank(struct drm_crtc *drm_crtc);
+void tegra_dc_disable_vblank(struct drm_crtc *drm_crtc);
+int tegra_dc_get_pipe(struct drm_crtc *drm_crtc);
+
+/* tegra_fb.c */
+struct fb_info *tegra_drm_fb_getinfo(struct drm_device *drm);
+struct tegra_bo *tegra_fb_get_plane(struct tegra_fb *fb, int idx);
+int tegra_drm_fb_create(struct drm_device *drm, struct drm_file *file,
+ struct drm_mode_fb_cmd2 *cmd, struct drm_framebuffer **fb_res);
+int tegra_drm_fb_init(struct drm_device *drm);
+void tegra_drm_fb_destroy(struct drm_device *drm);
+
+/* tegra_bo.c */
+struct tegra_bo;
+int tegra_bo_create(struct drm_device *drm, size_t size,
+ struct tegra_bo **res_bo);
+void tegra_bo_driver_register(struct drm_driver *drm_drv);
+
+#endif /* _TEGRA_DRM_H_ */
diff --git a/sys/arm/nvidia/drm2/tegra_drm_if.m b/sys/arm/nvidia/drm2/tegra_drm_if.m
new file mode 100644
index 000000000000..822db79ce9fa
--- /dev/null
+++ b/sys/arm/nvidia/drm2/tegra_drm_if.m
@@ -0,0 +1,68 @@
+#-
+# Copyright (c) 2015 Michal Meloun
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+#
+
+
+INTERFACE tegra_drm;
+HEADER {
+ struct tegra_drm;
+};
+
+
+/**
+ * Register client to host1x
+ */
+METHOD int register_client{
+ device_t host1x;
+ device_t client;
+};
+
+/**
+ * Deregister client to host1x
+ */
+METHOD int deregister_client{
+ device_t host1x;
+ device_t client;
+};
+
+/**
+ * Call client init method
+ */
+METHOD int init_client{
+ device_t client;
+ device_t host1x;
+ struct tegra_drm *drm;
+};
+
+/**
+ * Call client exit method
+ */
+METHOD int exit_client{
+ device_t client;
+ device_t host1x;
+ struct tegra_drm *drm;
+};
diff --git a/sys/arm/nvidia/drm2/tegra_drm_subr.c b/sys/arm/nvidia/drm2/tegra_drm_subr.c
new file mode 100644
index 000000000000..ab883445a2e1
--- /dev/null
+++ b/sys/arm/nvidia/drm2/tegra_drm_subr.c
@@ -0,0 +1,177 @@
+/*-
+ * Copyright (c) 2015 Michal Meloun
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+
+#include <machine/bus.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/drm2/drmP.h>
+#include <dev/drm2/drm_crtc.h>
+#include <dev/drm2/drm_crtc_helper.h>
+#include <dev/drm2/drm_edid.h>
+#include <dev/drm2/drm_fb_helper.h>
+#include <dev/gpio/gpiobusvar.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <arm/nvidia/drm2/tegra_drm.h>
+
+#include <gnu/dts/include/dt-bindings/gpio/gpio.h>
+
+int
+tegra_drm_connector_get_modes(struct drm_connector *connector)
+{
+ struct tegra_drm_encoder *output;
+ struct edid *edid = NULL;
+ int rv;
+
+ output = container_of(connector, struct tegra_drm_encoder,
+ connector);
+
+ /* Panel is first */
+ if (output->panel != NULL) {
+ /* XXX panel parsing */
+ return (0);
+ }
+
+ /* static EDID is second*/
+ edid = output->edid;
+
+ /* EDID from monitor is last */
+ if (edid == NULL)
+ edid = drm_get_edid(connector, output->ddc);
+
+ if (edid == NULL)
+ return (0);
+
+ /* Process EDID */
+ drm_mode_connector_update_edid_property(connector, edid);
+ rv = drm_add_edid_modes(connector, edid);
+ drm_edid_to_eld(connector, edid);
+ return (rv);
+}
+
+struct drm_encoder *
+tegra_drm_connector_best_encoder(struct drm_connector *connector)
+{
+ struct tegra_drm_encoder *output;
+
+ output = container_of(connector, struct tegra_drm_encoder,
+ connector);
+
+ return &(output->encoder);
+}
+
+enum drm_connector_status
+tegra_drm_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct tegra_drm_encoder *output;
+ bool active;
+ int rv;
+
+ output = container_of(connector, struct tegra_drm_encoder,
+ connector);
+ if (output->gpio_hpd == NULL) {
+ return ((output->panel != NULL) ?
+ connector_status_connected:
+ connector_status_disconnected);
+ }
+
+ rv = gpio_pin_is_active(output->gpio_hpd, &active);
+ if (rv != 0) {
+ device_printf(output->dev, " GPIO read failed: %d\n", rv);
+ return (connector_status_unknown);
+ }
+
+ return (active ?
+ connector_status_connected : connector_status_disconnected);
+}
+
+int
+tegra_drm_encoder_attach(struct tegra_drm_encoder *output, phandle_t node)
+{
+ int rv;
+ phandle_t ddc;
+
+ /* XXX parse output panel here */
+
+ rv = OF_getencprop_alloc(node, "nvidia,edid",
+ (void **)&output->edid);
+
+ /* EDID exist but have invalid size */
+ if ((rv >= 0) && (rv != sizeof(struct edid))) {
+ device_printf(output->dev,
+ "Malformed \"nvidia,edid\" property\n");
+ if (output->edid != NULL)
+ free(output->edid, M_OFWPROP);
+ return (ENXIO);
+ }
+
+ gpio_pin_get_by_ofw_property(output->dev, node, "nvidia,hpd-gpio",
+ &output->gpio_hpd);
+ ddc = 0;
+ OF_getencprop(node, "nvidia,ddc-i2c-bus", &ddc, sizeof(ddc));
+ if (ddc > 0)
+ output->ddc = OF_device_from_xref(ddc);
+ if ((output->edid == NULL) && (output->ddc == NULL))
+ return (ENXIO);
+
+ if (output->gpio_hpd != NULL) {
+ output->connector.polled =
+// DRM_CONNECTOR_POLL_HPD;
+ DRM_CONNECTOR_POLL_DISCONNECT |
+ DRM_CONNECTOR_POLL_CONNECT;
+ }
+
+ return (0);
+}
+
+int tegra_drm_encoder_init(struct tegra_drm_encoder *output,
+ struct tegra_drm *drm)
+{
+
+ if (output->panel) {
+ /* attach panel */
+ }
+ return (0);
+}
+
+int tegra_drm_encoder_exit(struct tegra_drm_encoder *output,
+ struct tegra_drm *drm)
+{
+
+ if (output->panel) {
+ /* detach panel */
+ }
+ return (0);
+}
diff --git a/sys/arm/nvidia/drm2/tegra_fb.c b/sys/arm/nvidia/drm2/tegra_fb.c
new file mode 100644
index 000000000000..ce5fbfd4cc38
--- /dev/null
+++ b/sys/arm/nvidia/drm2/tegra_fb.c
@@ -0,0 +1,338 @@
+/*-
+ * Copyright (c) 2016 Michal Meloun
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+
+#include <machine/bus.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/drm2/drmP.h>
+#include <dev/drm2/drm_crtc_helper.h>
+#include <dev/drm2/drm_fb_helper.h>
+
+#include <arm/nvidia/drm2/tegra_drm.h>
+
+static void
+fb_destroy(struct drm_framebuffer *drm_fb)
+{
+ struct tegra_fb *fb;
+ struct tegra_bo *bo;
+ unsigned int i;
+
+ fb = container_of(drm_fb, struct tegra_fb, drm_fb);
+ for (i = 0; i < fb->nplanes; i++) {
+ bo = fb->planes[i];
+ if (bo != NULL)
+ drm_gem_object_unreference_unlocked(&bo->gem_obj);
+ }
+
+ drm_framebuffer_cleanup(drm_fb);
+ free(fb->planes, DRM_MEM_DRIVER);
+}
+
+static int
+fb_create_handle(struct drm_framebuffer *drm_fb, struct drm_file *file,
+ unsigned int *handle)
+{
+ struct tegra_fb *fb;
+ int rv;
+
+ fb = container_of(drm_fb, struct tegra_fb, drm_fb);
+ rv = drm_gem_handle_create(file, &fb->planes[0]->gem_obj, handle);
+ return (rv);
+}
+
+/* XXX Probably not needed */
+static int
+fb_dirty(struct drm_framebuffer *fb, struct drm_file *file_priv,
+unsigned flags, unsigned color, struct drm_clip_rect *clips, unsigned num_clips)
+{
+
+ return (0);
+}
+
+static const struct drm_framebuffer_funcs fb_funcs = {
+ .destroy = fb_destroy,
+ .create_handle = fb_create_handle,
+ .dirty = fb_dirty,
+};
+
+static int
+fb_alloc(struct drm_device *drm, struct drm_mode_fb_cmd2 *mode_cmd,
+ struct tegra_bo **planes, int num_planes, struct tegra_fb **res_fb)
+{
+ struct tegra_fb *fb;
+ int i;
+ int rv;
+
+ fb = malloc(sizeof(*fb), DRM_MEM_DRIVER, M_WAITOK | M_ZERO);
+ fb->planes = malloc(num_planes * sizeof(*fb->planes), DRM_MEM_DRIVER,
+ M_WAITOK | M_ZERO);
+ fb->nplanes = num_planes;
+
+ drm_helper_mode_fill_fb_struct(&fb->drm_fb, mode_cmd);
+ for (i = 0; i < fb->nplanes; i++)
+ fb->planes[i] = planes[i];
+ rv = drm_framebuffer_init(drm, &fb->drm_fb, &fb_funcs);
+ if (rv < 0) {
+ device_printf(drm->dev,
+ "Cannot initialize frame buffer %d\n", rv);
+ free(fb->planes, DRM_MEM_DRIVER);
+ return (rv);
+ }
+ *res_fb = fb;
+ return (0);
+}
+
+static int
+tegra_fb_probe(struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ u_int bpp, size;
+ struct tegra_drm *drm;
+ struct tegra_fb *fb;
+ struct fb_info *info;
+ struct tegra_bo *bo;
+ struct drm_mode_fb_cmd2 mode_cmd;
+ struct drm_device *drm_dev;
+ int rv;
+
+ if (helper->fb != NULL)
+ return (0);
+
+ DRM_DEBUG_KMS("surface: %d x %d (bpp: %d)\n", sizes->surface_width,
+ sizes->surface_height, sizes->surface_bpp);
+
+ drm_dev = helper->dev;
+ fb = container_of(helper, struct tegra_fb, fb_helper);
+ drm = container_of(drm_dev, struct tegra_drm, drm_dev);
+ bpp = (sizes->surface_bpp + 7) / 8;
+
+ /* Create mode_cmd */
+ memset(&mode_cmd, 0, sizeof(mode_cmd));
+ mode_cmd.width = sizes->surface_width;
+ mode_cmd.height = sizes->surface_height;
+ mode_cmd.pitches[0] = roundup(sizes->surface_width * bpp,
+ drm->pitch_align);
+ mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+ sizes->surface_depth);
+ size = mode_cmd.pitches[0] * mode_cmd.height;
+
+ DRM_LOCK(drm_dev);
+ rv = tegra_bo_create(drm_dev, size, &bo);
+ DRM_UNLOCK(drm_dev);
+ if (rv != 0)
+ return (rv);
+
+ info = framebuffer_alloc();
+ if (info == NULL) {
+ device_printf(drm_dev->dev,
+ "Cannot allocate DRM framebuffer info.\n");
+ rv = -ENOMEM;
+ goto err_object;
+ }
+
+ rv = fb_alloc(drm_dev, &mode_cmd, &bo, 1, &fb);
+ if (rv != 0) {
+ device_printf(drm_dev->dev,
+ "Cannot allocate DRM framebuffer.\n");
+ goto err_fb;
+ }
+ helper->fb = &fb->drm_fb;
+ helper->fbdev = info;
+
+ /* Fill FB info */
+ info->fb_vbase = bo->vbase;
+ info->fb_pbase = bo->pbase;
+ info->fb_size = size;
+ info->fb_bpp = sizes->surface_bpp;
+ drm_fb_helper_fill_fix(info, fb->drm_fb.pitches[0], fb->drm_fb.depth);
+ drm_fb_helper_fill_var(info, helper, fb->drm_fb.width,
+ fb->drm_fb.height);
+
+ DRM_DEBUG_KMS("allocated %dx%d (s %dbits) fb size: %d, bo %p\n",
+ fb->drm_fb.width, fb->drm_fb.height, fb->drm_fb.depth,
+ size, bo);
+ return (1);
+err_fb:
+ drm_gem_object_unreference_unlocked(&bo->gem_obj);
+ framebuffer_release(info);
+err_object:
+ drm_gem_object_release(&bo->gem_obj);
+ return (rv);
+}
+
+static struct drm_fb_helper_funcs fb_helper_funcs = {
+ .fb_probe = tegra_fb_probe,
+};
+
+/*
+ * Exported functions
+ */
+struct fb_info *
+tegra_drm_fb_getinfo(struct drm_device *drm_dev)
+{
+ struct tegra_fb *fb;
+ struct tegra_drm *drm;
+
+ drm = container_of(drm_dev, struct tegra_drm, drm_dev);
+ fb = drm->fb;
+ if (fb == NULL)
+ return (NULL);
+ return (fb->fb_helper.fbdev);
+}
+
+struct tegra_bo *
+tegra_fb_get_plane(struct tegra_fb *fb, int idx)
+{
+
+ if (idx >= drm_format_num_planes(fb->drm_fb.pixel_format))
+ return (NULL);
+ if (idx >= fb->nplanes)
+ return (NULL);
+ return (fb->planes[idx]);
+}
+
+int
+tegra_drm_fb_init(struct drm_device *drm_dev)
+{
+ struct tegra_fb *fb;
+ struct tegra_drm *drm;
+ int rv;
+
+ drm = drm_dev->dev_private;
+ drm = container_of(drm_dev, struct tegra_drm, drm_dev);
+ fb = malloc(sizeof(*fb), DRM_MEM_DRIVER, M_WAITOK | M_ZERO);
+ drm->fb = fb;
+
+ fb->fb_helper.funcs = &fb_helper_funcs;
+ rv = drm_fb_helper_init(drm_dev, &fb->fb_helper,
+ drm_dev->mode_config.num_crtc, drm_dev->mode_config.num_connector);
+ if (rv != 0) {
+ device_printf(drm_dev->dev,
+ "Cannot initialize frame buffer %d\n", rv);
+ return (rv);
+ }
+
+ rv = drm_fb_helper_single_add_all_connectors(&fb->fb_helper);
+ if (rv != 0) {
+ device_printf(drm_dev->dev, "Cannot add all connectors: %d\n",
+ rv);
+ goto err_fini;
+ }
+
+ rv = drm_fb_helper_initial_config(&fb->fb_helper, 32);
+ if (rv != 0) {
+ device_printf(drm_dev->dev,
+ "Cannot set initial config: %d\n", rv);
+ goto err_fini;
+ }
+ /* XXXX Setup initial mode for FB */
+ /* drm_fb_helper_set_par(fb->fb_helper.fbdev); */
+ return 0;
+
+err_fini:
+ drm_fb_helper_fini(&fb->fb_helper);
+ return (rv);
+}
+
+int
+tegra_drm_fb_create(struct drm_device *drm, struct drm_file *file,
+ struct drm_mode_fb_cmd2 *cmd, struct drm_framebuffer **fb_res)
+{
+ int hsub, vsub, i;
+ int width, height, size, bpp;
+ struct tegra_bo *planes[4];
+ struct drm_gem_object *gem_obj;
+ struct tegra_fb *fb;
+ int rv, nplanes;
+
+ hsub = drm_format_horz_chroma_subsampling(cmd->pixel_format);
+ vsub = drm_format_vert_chroma_subsampling(cmd->pixel_format);
+
+ nplanes = drm_format_num_planes(cmd->pixel_format);
+ for (i = 0; i < nplanes; i++) {
+ width = cmd->width;
+ height = cmd->height;
+ if (i != 0) {
+ width /= hsub;
+ height /= vsub;
+ }
+ gem_obj = drm_gem_object_lookup(drm, file, cmd->handles[i]);
+ if (gem_obj == NULL) {
+ rv = -ENXIO;
+ goto fail;
+ }
+
+ bpp = drm_format_plane_cpp(cmd->pixel_format, i);
+ size = (height - 1) * cmd->pitches[i] +
+ width * bpp + cmd->offsets[i];
+ if (gem_obj->size < size) {
+ rv = -EINVAL;
+ goto fail;
+ }
+ planes[i] = container_of(gem_obj, struct tegra_bo, gem_obj);
+ }
+
+ rv = fb_alloc(drm, cmd, planes, nplanes, &fb);
+ if (rv != 0)
+ goto fail;
+
+ *fb_res = &fb->drm_fb;
+ return (0);
+
+fail:
+ while (i--)
+ drm_gem_object_unreference_unlocked(&planes[i]->gem_obj);
+ return (rv);
+}
+
+void
+tegra_drm_fb_destroy(struct drm_device *drm_dev)
+{
+ struct fb_info *info;
+ struct tegra_fb *fb;
+ struct tegra_drm *drm;
+
+ drm = container_of(drm_dev, struct tegra_drm, drm_dev);
+ fb = drm->fb;
+ if (fb == NULL)
+ return;
+ info = fb->fb_helper.fbdev;
+ drm_framebuffer_remove(&fb->drm_fb);
+ framebuffer_release(info);
+ drm_fb_helper_fini(&fb->fb_helper);
+ drm_framebuffer_cleanup(&fb->drm_fb);
+ free(fb, DRM_MEM_DRIVER);
+ drm->fb = NULL;
+}
diff --git a/sys/arm/nvidia/drm2/tegra_hdmi.c b/sys/arm/nvidia/drm2/tegra_hdmi.c
new file mode 100644
index 000000000000..0c9315ec31a0
--- /dev/null
+++ b/sys/arm/nvidia/drm2/tegra_hdmi.c
@@ -0,0 +1,1320 @@
+/*-
+ * Copyright (c) 2015 Michal Meloun
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/gpio.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/malloc.h>
+#include <sys/rman.h>
+#include <sys/sysctl.h>
+
+#include <machine/bus.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+#include <dev/extres/regulator/regulator.h>
+#include <dev/drm2/drmP.h>
+#include <dev/drm2/drm_crtc.h>
+#include <dev/drm2/drm_crtc_helper.h>
+#include <dev/drm2/drm_fb_helper.h>
+#include <dev/gpio/gpiobusvar.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <arm/nvidia/drm2/tegra_drm.h>
+#include <arm/nvidia/drm2/tegra_hdmi_reg.h>
+#include <arm/nvidia/drm2/tegra_dc_reg.h>
+#include <arm/nvidia/drm2/hdmi.h>
+
+#include "tegra_dc_if.h"
+#include "tegra_drm_if.h"
+
+#define WR4(_sc, _r, _v) bus_write_4((_sc)->mem_res, 4 * (_r), (_v))
+#define RD4(_sc, _r) bus_read_4((_sc)->mem_res, 4 * (_r))
+
+/* HDA stream format verb. */
+#define AC_FMT_CHAN_GET(x) (((x) >> 0) & 0xf)
+#define AC_FMT_CHAN_BITS_GET(x) (((x) >> 4) & 0x7)
+#define AC_FMT_DIV_GET(x) (((x) >> 8) & 0x7)
+#define AC_FMT_MUL_GET(x) (((x) >> 11) & 0x7)
+#define AC_FMT_BASE_44K (1 << 14)
+#define AC_FMT_TYPE_NON_PCM (1 << 15)
+
+#define HDMI_REKEY_DEFAULT 56
+#define HDMI_ELD_BUFFER_SIZE 96
+
+#define HDMI_DC_CLOCK_MULTIPIER 2
+
+struct audio_reg {
+ uint32_t audio_clk;
+ bus_size_t acr_reg;
+ bus_size_t nval_reg;
+ bus_size_t aval_reg;
+};
+
+static const struct audio_reg audio_regs[] =
+{
+ {
+ .audio_clk = 32000,
+ .acr_reg = HDMI_NV_PDISP_HDMI_ACR_0320_SUBPACK_LOW,
+ .nval_reg = HDMI_NV_PDISP_SOR_AUDIO_NVAL_0320,
+ .aval_reg = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0320,
+ },
+ {
+ .audio_clk = 44100,
+ .acr_reg = HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW,
+ .nval_reg = HDMI_NV_PDISP_SOR_AUDIO_NVAL_0441,
+ .aval_reg = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0441,
+ },
+ {
+ .audio_clk = 88200,
+ .acr_reg = HDMI_NV_PDISP_HDMI_ACR_0882_SUBPACK_LOW,
+ .nval_reg = HDMI_NV_PDISP_SOR_AUDIO_NVAL_0882,
+ .aval_reg = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0882,
+ },
+ {
+ .audio_clk = 176400,
+ .acr_reg = HDMI_NV_PDISP_HDMI_ACR_1764_SUBPACK_LOW,
+ .nval_reg = HDMI_NV_PDISP_SOR_AUDIO_NVAL_1764,
+ .aval_reg = HDMI_NV_PDISP_SOR_AUDIO_AVAL_1764,
+ },
+ {
+ .audio_clk = 48000,
+ .acr_reg = HDMI_NV_PDISP_HDMI_ACR_0480_SUBPACK_LOW,
+ .nval_reg = HDMI_NV_PDISP_SOR_AUDIO_NVAL_0480,
+ .aval_reg = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0480,
+ },
+ {
+ .audio_clk = 96000,
+ .acr_reg = HDMI_NV_PDISP_HDMI_ACR_0960_SUBPACK_LOW,
+ .nval_reg = HDMI_NV_PDISP_SOR_AUDIO_NVAL_0960,
+ .aval_reg = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0960,
+ },
+ {
+ .audio_clk = 192000,
+ .acr_reg = HDMI_NV_PDISP_HDMI_ACR_1920_SUBPACK_LOW,
+ .nval_reg = HDMI_NV_PDISP_SOR_AUDIO_NVAL_1920,
+ .aval_reg = HDMI_NV_PDISP_SOR_AUDIO_AVAL_1920,
+ },
+};
+
+struct tmds_config {
+ uint32_t pclk;
+ uint32_t pll0;
+ uint32_t pll1;
+ uint32_t drive_c;
+ uint32_t pe_c;
+ uint32_t peak_c;
+ uint32_t pad_ctls;
+};
+
+static const struct tmds_config tegra124_tmds_config[] =
+{
+ { /* 480p/576p / 25.2MHz/27MHz */
+ .pclk = 27000000,
+ .pll0 = 0x01003010,
+ .pll1 = 0x00301B00,
+ .drive_c = 0x1F1F1F1F,
+ .pe_c = 0x00000000,
+ .peak_c = 0x03030303,
+ .pad_ctls = 0x800034BB,
+ },
+ { /* 720p/1080i / 74.25MHz */
+ .pclk = 74250000,
+ .pll0 = 0x01003110,
+ .pll1 = 0x00301500,
+ .drive_c = 0x2C2C2C2C,
+ .pe_c = 0x00000000,
+ .peak_c = 0x07070707,
+ .pad_ctls = 0x800034BB,
+ },
+ { /* 1080p / 148.5MHz */
+ .pclk = 148500000,
+ .pll0 = 0x01003310,
+ .pll1 = 0x00301500,
+ .drive_c = 0x33333333,
+ .pe_c = 0x00000000,
+ .peak_c = 0x0C0C0C0C,
+ .pad_ctls = 0x800034BB,
+ },
+ { /* 2216p / 297MHz */
+ .pclk = UINT_MAX,
+ .pll0 = 0x01003F10,
+ .pll1 = 0x00300F00,
+ .drive_c = 0x37373737,
+ .pe_c = 0x00000000,
+ .peak_c = 0x17171717,
+ .pad_ctls = 0x800036BB,
+ },
+};
+
+struct hdmi_softc {
+ device_t dev;
+ struct resource *mem_res;
+ struct resource *irq_res;
+ void *irq_ih;
+
+ clk_t clk_parent;
+ clk_t clk_hdmi;
+ hwreset_t hwreset_hdmi;
+ regulator_t supply_hdmi;
+ regulator_t supply_pll;
+ regulator_t supply_vdd;
+
+ uint64_t pclk;
+ boolean_t hdmi_mode;
+
+ int audio_src_type;
+ int audio_freq;
+ int audio_chans;
+
+ struct tegra_drm *drm;
+ struct tegra_drm_encoder output;
+
+ const struct tmds_config *tmds_config;
+ int n_tmds_configs;
+};
+
+static struct ofw_compat_data compat_data[] = {
+ {"nvidia,tegra124-hdmi", 1},
+ {NULL, 0},
+};
+
+/* These functions have been copied from newer version of drm_edid.c */
+/* ELD Header Block */
+#define DRM_ELD_HEADER_BLOCK_SIZE 4
+#define DRM_ELD_BASELINE_ELD_LEN 2 /* in dwords! */
+static int drm_eld_size(const uint8_t *eld)
+{
+ return DRM_ELD_HEADER_BLOCK_SIZE + eld[DRM_ELD_BASELINE_ELD_LEN] * 4;
+}
+
+static int
+drm_hdmi_avi_infoframe_from_display_mode(struct hdmi_avi_infoframe *frame,
+ struct drm_display_mode *mode)
+{
+ int rv;
+
+ if (!frame || !mode)
+ return -EINVAL;
+
+ rv = hdmi_avi_infoframe_init(frame);
+ if (rv < 0)
+ return rv;
+
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+ frame->pixel_repeat = 1;
+
+ frame->video_code = drm_match_cea_mode(mode);
+
+ frame->picture_aspect = HDMI_PICTURE_ASPECT_NONE;
+#ifdef FREEBSD_NOTYET
+ /*
+ * Populate picture aspect ratio from either
+ * user input (if specified) or from the CEA mode list.
+ */
+ if (mode->picture_aspect_ratio == HDMI_PICTURE_ASPECT_4_3 ||
+ mode->picture_aspect_ratio == HDMI_PICTURE_ASPECT_16_9)
+ frame->picture_aspect = mode->picture_aspect_ratio;
+ else if (frame->video_code > 0)
+ frame->picture_aspect = drm_get_cea_aspect_ratio(
+ frame->video_code);
+#endif
+
+ frame->active_aspect = HDMI_ACTIVE_ASPECT_PICTURE;
+ frame->scan_mode = HDMI_SCAN_MODE_UNDERSCAN;
+
+ return 0;
+}
+/* --------------------------------------------------------------------- */
+
+static int
+hdmi_setup_clock(struct tegra_drm_encoder *output, clk_t clk, uint64_t pclk)
+{
+ struct hdmi_softc *sc;
+ uint64_t freq;
+ int rv;
+
+ sc = device_get_softc(output->dev);
+
+ /* Disable consumers clock for while. */
+ rv = clk_disable(sc->clk_hdmi);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot disable 'hdmi' clock\n");
+ return (rv);
+ }
+ rv = clk_disable(clk);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot disable display clock\n");
+ return (rv);
+ }
+
+ /* Set frequency for Display Controller PLL. */
+ freq = HDMI_DC_CLOCK_MULTIPIER * pclk;
+ rv = clk_set_freq(sc->clk_parent, freq, 0);
+ if (rv != 0) {
+ device_printf(output->dev,
+ "Cannot set display pixel frequency\n");
+ return (rv);
+ }
+
+ /* Reparent display controller */
+ rv = clk_set_parent_by_clk(clk, sc->clk_parent);
+ if (rv != 0) {
+ device_printf(output->dev, "Cannot set parent clock\n");
+ return (rv);
+ }
+ rv = clk_set_freq(clk, freq, 0);
+ if (rv != 0) {
+ device_printf(output->dev,
+ "Cannot set display controller frequency\n");
+ return (rv);
+ }
+ rv = clk_set_freq(sc->clk_hdmi, pclk, 0);
+ if (rv != 0) {
+ device_printf(output->dev,
+ "Cannot set display controller frequency\n");
+ return (rv);
+ }
+
+ /* And reenable consumers clock. */
+ rv = clk_enable(clk);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot enable display clock\n");
+ return (rv);
+ }
+ rv = clk_enable(sc->clk_hdmi);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot enable 'hdmi' clock\n");
+ return (rv);
+ }
+
+ rv = clk_get_freq(clk, &freq);
+ if (rv != 0) {
+ device_printf(output->dev,
+ "Cannot get display controller frequency\n");
+ return (rv);
+ }
+
+ DRM_DEBUG_KMS("DC frequency: %llu\n", freq);
+
+ return (0);
+}
+
+/* -------------------------------------------------------------------
+ *
+ * Infoframes.
+ *
+ */
+static void
+avi_setup_infoframe(struct hdmi_softc *sc, struct drm_display_mode *mode)
+{
+ struct hdmi_avi_infoframe frame;
+ uint8_t buf[17], *hdr, *pb;
+ ssize_t rv;
+
+ rv = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
+ if (rv < 0) {
+ device_printf(sc->dev, "Cannot setup AVI infoframe: %zd\n", rv);
+ return;
+ }
+ rv = hdmi_avi_infoframe_pack(&frame, buf, sizeof(buf));
+ if (rv < 0) {
+ device_printf(sc->dev, "Cannot pack AVI infoframe: %zd\n", rv);
+ return;
+ }
+ hdr = buf + 0;
+ pb = buf + 3;
+ WR4(sc, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_HEADER,
+ (hdr[2] << 16) | (hdr[1] << 8) | (hdr[0] << 0));
+ WR4(sc, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_LOW,
+ (pb[3] << 24) |(pb[2] << 16) | (pb[1] << 8) | (pb[0] << 0));
+ WR4(sc, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_HIGH,
+ (pb[6] << 16) | (pb[5] << 8) | (pb[4] << 0));
+ WR4(sc, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_LOW,
+ (pb[10] << 24) |(pb[9] << 16) | (pb[8] << 8) | (pb[7] << 0));
+ WR4(sc, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_HIGH,
+ (pb[13] << 16) | (pb[12] << 8) | (pb[11] << 0));
+
+ WR4(sc, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL,
+ AVI_INFOFRAME_CTRL_ENABLE);
+}
+
+static void
+audio_setup_infoframe(struct hdmi_softc *sc)
+{
+ struct hdmi_audio_infoframe frame;
+ uint8_t buf[14], *hdr, *pb;
+ ssize_t rv;
+
+ rv = hdmi_audio_infoframe_init(&frame);
+ frame.channels = sc->audio_chans;
+ rv = hdmi_audio_infoframe_pack(&frame, buf, sizeof(buf));
+ if (rv < 0) {
+ device_printf(sc->dev, "Cannot pack audio infoframe\n");
+ return;
+ }
+ hdr = buf + 0;
+ pb = buf + 3;
+ WR4(sc, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_HEADER,
+ (hdr[2] << 16) | (hdr[1] << 8) | (hdr[0] << 0));
+ WR4(sc, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_LOW,
+ (pb[3] << 24) |(pb[2] << 16) | (pb[1] << 8) | (pb[0] << 0));
+ WR4(sc, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_HIGH,
+ (pb[5] << 8) | (pb[4] << 0));
+
+ WR4(sc, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL,
+ AUDIO_INFOFRAME_CTRL_ENABLE);
+}
+
+/* -------------------------------------------------------------------
+ *
+ * Audio
+ *
+ */
+static void
+init_hda_eld(struct hdmi_softc *sc)
+{
+ size_t size;
+ int i ;
+ uint32_t val;
+
+ size = drm_eld_size(sc->output.connector.eld);
+ for (i = 0; i < HDMI_ELD_BUFFER_SIZE; i++) {
+ val = i << 8;
+ if (i < size)
+ val |= sc->output.connector.eld[i];
+ WR4(sc, HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR, val);
+ }
+ WR4(sc,HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE,
+ SOR_AUDIO_HDA_PRESENSE_VALID | SOR_AUDIO_HDA_PRESENSE_PRESENT);
+}
+
+static int
+get_audio_regs(int freq, bus_size_t *acr_reg, bus_size_t *nval_reg,
+ bus_size_t *aval_reg)
+{
+ int i;
+ const struct audio_reg *reg;
+
+ for (i = 0; i < nitems(audio_regs) ; i++) {
+ reg = audio_regs + i;
+ if (reg->audio_clk == freq) {
+ if (acr_reg != NULL)
+ *acr_reg = reg->acr_reg;
+ if (nval_reg != NULL)
+ *nval_reg = reg->nval_reg;
+ if (aval_reg != NULL)
+ *aval_reg = reg->aval_reg;
+ return (0);
+ }
+ }
+ return (ERANGE);
+}
+
+#define FR_BITS 16
+#define TO_FFP(x) (((int64_t)(x)) << FR_BITS)
+#define TO_INT(x) ((int)((x) >> FR_BITS))
+static int
+get_hda_cts_n(uint32_t audio_freq_hz, uint32_t pixclk_freq_hz,
+ uint32_t *best_cts, uint32_t *best_n, uint32_t *best_a)
+{
+ int min_n;
+ int max_n;
+ int ideal_n;
+ int n;
+ int cts;
+ int aval;
+ int64_t err_f;
+ int64_t min_err_f;
+ int64_t cts_f;
+ int64_t aval_f;
+ int64_t half_f; /* constant 0.5 */
+ bool better_n;
+
+ /*
+ * All floats are in fixed I48.16 format.
+ *
+ * Ideal ACR interval is 1000 hz (1 ms);
+ * acceptable is 300 hz .. 1500 hz
+ */
+ min_n = 128 * audio_freq_hz / 1500;
+ max_n = 128 * audio_freq_hz / 300;
+ ideal_n = 128 * audio_freq_hz / 1000;
+ min_err_f = TO_FFP(100);
+ half_f = TO_FFP(1) / 2;
+
+ *best_n = 0;
+ *best_cts = 0;
+ *best_a = 0;
+
+ for (n = min_n; n <= max_n; n++) {
+ cts_f = TO_FFP(pixclk_freq_hz);
+ cts_f *= n;
+ cts_f /= 128 * audio_freq_hz;
+ cts = TO_INT(cts_f + half_f); /* round */
+ err_f = cts_f - TO_FFP(cts);
+ if (err_f < 0)
+ err_f = -err_f;
+ aval_f = TO_FFP(24000000);
+ aval_f *= n;
+ aval_f /= 128 * audio_freq_hz;
+ aval = TO_INT(aval_f); /* truncate */
+
+ better_n = abs(n - ideal_n) < abs((int)(*best_n) - ideal_n);
+ if (TO_FFP(aval) == aval_f &&
+ (err_f < min_err_f || (err_f == min_err_f && better_n))) {
+ min_err_f = err_f;
+ *best_n = (uint32_t)n;
+ *best_cts = (uint32_t)cts;
+ *best_a = (uint32_t)aval;
+
+ if (err_f == 0 && n == ideal_n)
+ break;
+ }
+ }
+ return (0);
+}
+#undef FR_BITS
+#undef TO_FFP
+#undef TO_INT
+
+static int
+audio_setup(struct hdmi_softc *sc)
+{
+ uint32_t val;
+ uint32_t audio_n;
+ uint32_t audio_cts;
+ uint32_t audio_aval;
+ uint64_t hdmi_freq;
+ bus_size_t aval_reg;
+ int rv;
+
+ if (!sc->hdmi_mode)
+ return (ENOTSUP);
+ rv = get_audio_regs(sc->audio_freq, NULL, NULL, &aval_reg);
+ if (rv != 0) {
+ device_printf(sc->dev, "Unsupported audio frequency.\n");
+ return (rv);
+ }
+
+ rv = clk_get_freq(sc->clk_hdmi, &hdmi_freq);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot get hdmi frequency: %d\n", rv);
+ return (rv);
+ }
+
+ rv = get_hda_cts_n(sc->audio_freq, hdmi_freq, &audio_cts, &audio_n,
+ &audio_aval);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot compute audio coefs: %d\n", rv);
+ return (rv);
+ }
+
+ /* Audio infoframe. */
+ audio_setup_infoframe(sc);
+ /* Setup audio source */
+ WR4(sc, HDMI_NV_PDISP_SOR_AUDIO_CNTRL0,
+ SOR_AUDIO_CNTRL0_SOURCE_SELECT(sc->audio_src_type) |
+ SOR_AUDIO_CNTRL0_INJECT_NULLSMPL);
+
+ val = RD4(sc, HDMI_NV_PDISP_SOR_AUDIO_SPARE0);
+ val |= SOR_AUDIO_SPARE0_HBR_ENABLE;
+ WR4(sc, HDMI_NV_PDISP_SOR_AUDIO_SPARE0, val);
+
+ WR4(sc, HDMI_NV_PDISP_HDMI_ACR_CTRL, 0);
+
+ WR4(sc, HDMI_NV_PDISP_AUDIO_N,
+ AUDIO_N_RESETF |
+ AUDIO_N_GENERATE_ALTERNATE |
+ AUDIO_N_VALUE(audio_n - 1));
+
+ WR4(sc, HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH,
+ ACR_SUBPACK_N(audio_n) | ACR_ENABLE);
+
+ WR4(sc, HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW,
+ ACR_SUBPACK_CTS(audio_cts));
+
+ WR4(sc, HDMI_NV_PDISP_HDMI_SPARE,
+ SPARE_HW_CTS | SPARE_FORCE_SW_CTS | SPARE_CTS_RESET_VAL(1));
+
+ val = RD4(sc, HDMI_NV_PDISP_AUDIO_N);
+ val &= ~AUDIO_N_RESETF;
+ WR4(sc, HDMI_NV_PDISP_AUDIO_N, val);
+
+ WR4(sc, aval_reg, audio_aval);
+
+ return (0);
+}
+
+static void
+audio_disable(struct hdmi_softc *sc) {
+ uint32_t val;
+
+ /* Disable audio */
+ val = RD4(sc, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
+ val &= ~GENERIC_CTRL_AUDIO;
+ WR4(sc, HDMI_NV_PDISP_HDMI_GENERIC_CTRL, val);
+
+ /* Disable audio infoframes */
+ val = RD4(sc, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL);
+ val &= ~AUDIO_INFOFRAME_CTRL_ENABLE;
+ WR4(sc, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL, val);
+}
+
+static void
+audio_enable(struct hdmi_softc *sc) {
+ uint32_t val;
+
+ if (!sc->hdmi_mode)
+ audio_disable(sc);
+
+ /* Enable audio infoframes */
+ val = RD4(sc, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL);
+ val |= AUDIO_INFOFRAME_CTRL_ENABLE;
+ WR4(sc, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL, val);
+
+ /* Enable audio */
+ val = RD4(sc, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
+ val |= GENERIC_CTRL_AUDIO;
+ WR4(sc, HDMI_NV_PDISP_HDMI_GENERIC_CTRL, val);
+}
+
+/* -------------------------------------------------------------------
+ *
+ * HDMI.
+ *
+ */
+ /* Process format change notification from HDA */
+static void
+hda_intr(struct hdmi_softc *sc)
+{
+ uint32_t val;
+ int rv;
+
+ if (!sc->hdmi_mode)
+ return;
+
+ val = RD4(sc, HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0);
+ if ((val & (1 << 30)) == 0) {
+ audio_disable(sc);
+ return;
+ }
+
+ /* XXX Move this to any header */
+ /* Keep in sync with HDA */
+ sc->audio_freq = val & 0x00FFFFFF;
+ sc->audio_chans = (val >> 24) & 0x0f;
+ DRM_DEBUG_KMS("%d channel(s) at %dHz\n", sc->audio_chans,
+ sc->audio_freq);
+
+ rv = audio_setup(sc);
+ if (rv != 0) {
+ audio_disable(sc);
+ return;
+ }
+
+ audio_enable(sc);
+}
+
+static void
+tmds_init(struct hdmi_softc *sc, const struct tmds_config *tmds)
+{
+
+ WR4(sc, HDMI_NV_PDISP_SOR_PLL0, tmds->pll0);
+ WR4(sc, HDMI_NV_PDISP_SOR_PLL1, tmds->pll1);
+ WR4(sc, HDMI_NV_PDISP_PE_CURRENT, tmds->pe_c);
+ WR4(sc, HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT, tmds->drive_c);
+ WR4(sc, HDMI_NV_PDISP_SOR_IO_PEAK_CURRENT, tmds->peak_c);
+ WR4(sc, HDMI_NV_PDISP_SOR_PAD_CTLS0, tmds->pad_ctls);
+}
+
+static int
+hdmi_sor_start(struct hdmi_softc *sc, struct drm_display_mode *mode)
+{
+ int i;
+ uint32_t val;
+
+ /* Enable TMDS macro */
+ val = RD4(sc, HDMI_NV_PDISP_SOR_PLL0);
+ val &= ~SOR_PLL0_PWR;
+ val &= ~SOR_PLL0_VCOPD;
+ val &= ~SOR_PLL0_PULLDOWN;
+ WR4(sc, HDMI_NV_PDISP_SOR_PLL0, val);
+ DELAY(10);
+
+ val = RD4(sc, HDMI_NV_PDISP_SOR_PLL0);
+ val &= ~SOR_PLL0_PDBG;
+ WR4(sc, HDMI_NV_PDISP_SOR_PLL0, val);
+
+ WR4(sc, HDMI_NV_PDISP_SOR_PWR, SOR_PWR_SETTING_NEW);
+ WR4(sc, HDMI_NV_PDISP_SOR_PWR, 0);
+
+ /* Wait until SOR is ready */
+ for (i = 1000; i > 0; i--) {
+ val = RD4(sc, HDMI_NV_PDISP_SOR_PWR);
+ if ((val & SOR_PWR_SETTING_NEW) == 0)
+ break;
+ DELAY(10);
+ }
+ if (i <= 0) {
+ device_printf(sc->dev, "Timeouted while enabling SOR power.\n");
+ return (ETIMEDOUT);
+ }
+
+ val = SOR_STATE2_ASY_OWNER(ASY_OWNER_HEAD0) |
+ SOR_STATE2_ASY_SUBOWNER(SUBOWNER_BOTH) |
+ SOR_STATE2_ASY_CRCMODE(ASY_CRCMODE_COMPLETE) |
+ SOR_STATE2_ASY_PROTOCOL(ASY_PROTOCOL_SINGLE_TMDS_A);
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ val |= SOR_STATE2_ASY_HSYNCPOL_NEG;
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ val |= SOR_STATE2_ASY_VSYNCPOL_NEG;
+ WR4(sc, HDMI_NV_PDISP_SOR_STATE2, val);
+
+ WR4(sc, HDMI_NV_PDISP_SOR_STATE1, SOR_STATE1_ASY_ORMODE_NORMAL |
+ SOR_STATE1_ASY_HEAD_OPMODE(ASY_HEAD_OPMODE_AWAKE));
+
+ WR4(sc, HDMI_NV_PDISP_SOR_STATE0, 0);
+ WR4(sc, HDMI_NV_PDISP_SOR_STATE0, SOR_STATE0_UPDATE);
+
+ val = RD4(sc, HDMI_NV_PDISP_SOR_STATE1);
+ val |= SOR_STATE1_ATTACHED;
+ WR4(sc, HDMI_NV_PDISP_SOR_STATE1, val);
+
+ WR4(sc, HDMI_NV_PDISP_SOR_STATE0, 0);
+
+ return 0;
+}
+
+static int
+hdmi_disable(struct hdmi_softc *sc)
+{
+ struct tegra_crtc *crtc;
+ device_t dc;
+ uint32_t val;
+
+ dc = NULL;
+ if (sc->output.encoder.crtc != NULL) {
+ crtc = container_of(sc->output.encoder.crtc, struct tegra_crtc,
+ drm_crtc);
+ dc = crtc->dev;
+ }
+
+ if (dc != NULL) {
+ TEGRA_DC_HDMI_ENABLE(dc, false);
+ TEGRA_DC_DISPLAY_ENABLE(dc, false);
+ }
+ audio_disable(sc);
+ val = RD4(sc, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL);
+ val &= ~AVI_INFOFRAME_CTRL_ENABLE;
+ WR4(sc, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL, val);
+
+ /* Disable interrupts */
+ WR4(sc, HDMI_NV_PDISP_INT_ENABLE, 0);
+ WR4(sc, HDMI_NV_PDISP_INT_MASK, 0);
+
+ return (0);
+}
+
+static int
+hdmi_enable(struct hdmi_softc *sc)
+{
+ uint64_t freq;
+ struct drm_display_mode *mode;
+ struct tegra_crtc *crtc;
+ uint32_t val, h_sync_width, h_back_porch, h_front_porch, h_pulse_start;
+ uint32_t h_max_ac_packet, div8_2;
+ device_t dc;
+ int i, rv;
+
+ mode = &sc->output.encoder.crtc->mode;
+ crtc = container_of(sc->output.encoder.crtc, struct tegra_crtc,
+ drm_crtc);
+ dc = crtc->dev;
+
+ /* Compute all timings first. */
+ sc->pclk = mode->clock * 1000;
+ h_sync_width = mode->hsync_end - mode->hsync_start;
+ h_back_porch = mode->htotal - mode->hsync_end;
+ h_front_porch = mode->hsync_start - mode->hdisplay;
+ h_pulse_start = 1 + h_sync_width + h_back_porch - 10;
+ h_max_ac_packet = (h_sync_width + h_back_porch + h_front_porch -
+ HDMI_REKEY_DEFAULT - 18) / 32;
+
+ /* Check if HDMI device is connected and detected. */
+ if (sc->output.connector.edid_blob_ptr == NULL) {
+ sc->hdmi_mode = false;
+ } else {
+ sc->hdmi_mode = drm_detect_hdmi_monitor(
+ (struct edid *)sc->output.connector.edid_blob_ptr->data);
+ }
+
+ /* Get exact HDMI pixel frequency. */
+ rv = clk_get_freq(sc->clk_hdmi, &freq);
+ if (rv != 0) {
+ device_printf(sc->dev,
+ "Cannot get 'hdmi' clock frequency\n");
+ return (rv);
+ }
+ DRM_DEBUG_KMS("HDMI frequency: %llu Hz\n", freq);
+
+ /* Wakeup SOR power */
+ val = RD4(sc, HDMI_NV_PDISP_SOR_PLL0);
+ val &= ~SOR_PLL0_PDBG;
+ WR4(sc, HDMI_NV_PDISP_SOR_PLL0, val);
+ DELAY(10);
+
+ val = RD4(sc, HDMI_NV_PDISP_SOR_PLL0);
+ val &= ~SOR_PLL0_PWR;
+ WR4(sc, HDMI_NV_PDISP_SOR_PLL0, val);
+
+ /* Setup timings */
+ TEGRA_DC_SETUP_TIMING(dc, h_pulse_start);
+ WR4(sc, HDMI_NV_PDISP_HDMI_VSYNC_WINDOW,
+ VSYNC_WINDOW_START(0x200) | VSYNC_WINDOW_END(0x210) |
+ VSYNC_WINDOW_ENABLE);
+
+ /* Setup video source and adjust video range */
+ val = 0;
+ if (crtc->nvidia_head != 0)
+ HDMI_SRC_DISPLAYB;
+ if ((mode->hdisplay != 640) || (mode->vdisplay != 480))
+ val |= ARM_VIDEO_RANGE_LIMITED;
+ WR4(sc, HDMI_NV_PDISP_INPUT_CONTROL, val);
+
+ /* Program SOR reference clock - it uses 8.2 fractional divisor */
+ div8_2 = (freq * 4) / 1000000;
+ val = SOR_REFCLK_DIV_INT(div8_2 >> 2) | SOR_REFCLK_DIV_FRAC(div8_2);
+ WR4(sc, HDMI_NV_PDISP_SOR_REFCLK, val);
+
+ /* Setup audio */
+ if (sc->hdmi_mode) {
+ rv = audio_setup(sc);
+ if (rv != 0)
+ sc->hdmi_mode = false;
+ }
+
+ /* Init HDA ELD */
+ init_hda_eld(sc);
+ val = HDMI_CTRL_REKEY(HDMI_REKEY_DEFAULT);
+ val |= HDMI_CTRL_MAX_AC_PACKET(h_max_ac_packet);
+ if (sc->hdmi_mode)
+ val |= HDMI_CTRL_ENABLE;
+ WR4(sc, HDMI_NV_PDISP_HDMI_CTRL, val);
+
+ /* Setup TMDS */
+ for (i = 0; i < sc->n_tmds_configs; i++) {
+ if (sc->pclk <= sc->tmds_config[i].pclk) {
+ tmds_init(sc, sc->tmds_config + i);
+ break;
+ }
+ }
+
+ /* Program sequencer. */
+ WR4(sc, HDMI_NV_PDISP_SOR_SEQ_CTL,
+ SOR_SEQ_PU_PC(0) | SOR_SEQ_PU_PC_ALT(0) |
+ SOR_SEQ_PD_PC(8) | SOR_SEQ_PD_PC_ALT(8));
+
+ val = SOR_SEQ_INST_WAIT_TIME(1) |
+ SOR_SEQ_INST_WAIT_UNITS(WAIT_UNITS_VSYNC) |
+ SOR_SEQ_INST_HALT |
+ SOR_SEQ_INST_DRIVE_PWM_OUT_LO;
+ WR4(sc, HDMI_NV_PDISP_SOR_SEQ_INST(0), val);
+ WR4(sc, HDMI_NV_PDISP_SOR_SEQ_INST(8), val);
+
+ val = RD4(sc,HDMI_NV_PDISP_SOR_CSTM);
+ val &= ~SOR_CSTM_LVDS_ENABLE;
+ val &= ~SOR_CSTM_ROTCLK(~0);
+ val |= SOR_CSTM_ROTCLK(2);
+ val &= ~SOR_CSTM_MODE(~0);
+ val |= SOR_CSTM_MODE(CSTM_MODE_TMDS);
+ val |= SOR_CSTM_PLLDIV;
+ WR4(sc, HDMI_NV_PDISP_SOR_CSTM, val);
+
+ TEGRA_DC_DISPLAY_ENABLE(dc, false);
+
+ rv = hdmi_sor_start(sc, mode);
+ if (rv != 0)
+ return (rv);
+
+ TEGRA_DC_HDMI_ENABLE(dc, true);
+ TEGRA_DC_DISPLAY_ENABLE(dc, true);
+
+ /* Enable HDA codec interrupt */
+ WR4(sc, HDMI_NV_PDISP_INT_MASK, INT_CODEC_SCRATCH0);
+ WR4(sc, HDMI_NV_PDISP_INT_ENABLE, INT_CODEC_SCRATCH0);
+
+ if (sc->hdmi_mode) {
+ avi_setup_infoframe(sc, mode);
+ audio_enable(sc);
+ }
+
+ return (0);
+}
+
+/* -------------------------------------------------------------------
+ *
+ * DRM Interface.
+ *
+ */
+static enum drm_mode_status
+hdmi_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct tegra_drm_encoder *output;
+ struct hdmi_softc *sc;
+ int rv;
+ uint64_t freq;
+
+ output = container_of(connector, struct tegra_drm_encoder,
+ connector);
+ sc = device_get_softc(output->dev);
+
+ freq = HDMI_DC_CLOCK_MULTIPIER * mode->clock * 1000;
+ rv = clk_test_freq(sc->clk_parent, freq, 0);
+ DRM_DEBUG_KMS("Test HDMI frequency: %u kHz, rv: %d\n", mode->clock, rv);
+ if (rv != 0)
+ return (MODE_NOCLOCK);
+
+ return (MODE_OK);
+}
+
+static const struct drm_connector_helper_funcs hdmi_connector_helper_funcs = {
+ .get_modes = tegra_drm_connector_get_modes,
+ .mode_valid = hdmi_connector_mode_valid,
+ .best_encoder = tegra_drm_connector_best_encoder,
+};
+
+static const struct drm_connector_funcs hdmi_connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .detect = tegra_drm_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+};
+
+static const struct drm_encoder_funcs hdmi_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+static void
+hdmi_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+
+ /* Empty function. */
+}
+
+static bool
+hdmi_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted)
+{
+
+ return (true);
+}
+
+static void
+hdmi_encoder_prepare(struct drm_encoder *encoder)
+{
+
+ /* Empty function. */
+}
+
+static void
+hdmi_encoder_commit(struct drm_encoder *encoder)
+{
+
+ /* Empty function. */
+}
+
+static void
+hdmi_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode, struct drm_display_mode *adjusted)
+{
+ struct tegra_drm_encoder *output;
+ struct hdmi_softc *sc;
+ int rv;
+
+ output = container_of(encoder, struct tegra_drm_encoder, encoder);
+ sc = device_get_softc(output->dev);
+ rv = hdmi_enable(sc);
+ if (rv != 0)
+ device_printf(sc->dev, "Cannot enable HDMI port\n");
+
+}
+
+static void
+hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+ struct tegra_drm_encoder *output;
+ struct hdmi_softc *sc;
+ int rv;
+
+ output = container_of(encoder, struct tegra_drm_encoder, encoder);
+ sc = device_get_softc(output->dev);
+ if (sc == NULL)
+ return;
+ rv = hdmi_disable(sc);
+ if (rv != 0)
+ device_printf(sc->dev, "Cannot disable HDMI port\n");
+}
+
+static const struct drm_encoder_helper_funcs hdmi_encoder_helper_funcs = {
+ .dpms = hdmi_encoder_dpms,
+ .mode_fixup = hdmi_encoder_mode_fixup,
+ .prepare = hdmi_encoder_prepare,
+ .commit = hdmi_encoder_commit,
+ .mode_set = hdmi_encoder_mode_set,
+ .disable = hdmi_encoder_disable,
+};
+
+/* -------------------------------------------------------------------
+ *
+ * Bus and infrastructure.
+ *
+ */
+static int
+hdmi_init_client(device_t dev, device_t host1x, struct tegra_drm *drm)
+{
+ struct hdmi_softc *sc;
+ phandle_t node;
+ int rv;
+
+ sc = device_get_softc(dev);
+ node = ofw_bus_get_node(sc->dev);
+ sc->drm = drm;
+ sc->output.setup_clock = &hdmi_setup_clock;
+
+ rv = tegra_drm_encoder_attach(&sc->output, node);
+ if (rv != 0) {
+ device_printf(dev, "Cannot attach output connector\n");
+ return(ENXIO);
+ }
+
+ /* Connect this encoder + connector to DRM. */
+ drm_connector_init(&drm->drm_dev, &sc->output.connector,
+ &hdmi_connector_funcs, DRM_MODE_CONNECTOR_HDMIA);
+
+ drm_connector_helper_add(&sc->output.connector,
+ &hdmi_connector_helper_funcs);
+
+ sc->output.connector.dpms = DRM_MODE_DPMS_OFF;
+
+ drm_encoder_init(&drm->drm_dev, &sc->output.encoder,
+ &hdmi_encoder_funcs, DRM_MODE_ENCODER_TMDS);
+
+ drm_encoder_helper_add(&sc->output.encoder, &hdmi_encoder_helper_funcs);
+
+ drm_mode_connector_attach_encoder(&sc->output.connector,
+ &sc->output.encoder);
+
+ rv = tegra_drm_encoder_init(&sc->output, drm);
+ if (rv < 0) {
+ device_printf(sc->dev, "Unable to init HDMI output\n");
+ return (rv);
+ }
+ sc->output.encoder.possible_crtcs = 0x3;
+ return (0);
+}
+
+static int
+hdmi_exit_client(device_t dev, device_t host1x, struct tegra_drm *drm)
+{
+ struct hdmi_softc *sc;
+
+ sc = device_get_softc(dev);
+ tegra_drm_encoder_exit(&sc->output, drm);
+ return (0);
+}
+
+static int
+get_fdt_resources(struct hdmi_softc *sc, phandle_t node)
+{
+ int rv;
+
+ rv = regulator_get_by_ofw_property(sc->dev, 0, "hdmi-supply",
+ &sc->supply_hdmi);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot get 'hdmi' regulator\n");
+ return (ENXIO);
+ }
+ rv = regulator_get_by_ofw_property(sc->dev,0, "pll-supply",
+ &sc->supply_pll);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot get 'pll' regulator\n");
+ return (ENXIO);
+ }
+ rv = regulator_get_by_ofw_property(sc->dev, 0, "vdd-supply",
+ &sc->supply_vdd);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot get 'vdd' regulator\n");
+ return (ENXIO);
+ }
+
+ rv = hwreset_get_by_ofw_name(sc->dev, 0, "hdmi", &sc->hwreset_hdmi);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot get 'hdmi' reset\n");
+ return (ENXIO);
+ }
+ rv = clk_get_by_ofw_name(sc->dev, 0, "parent", &sc->clk_parent);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot get 'parent' clock\n");
+ return (ENXIO);
+ }
+ rv = clk_get_by_ofw_name(sc->dev, 0, "hdmi", &sc->clk_hdmi);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot get 'hdmi' clock\n");
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+static int
+enable_fdt_resources(struct hdmi_softc *sc)
+{
+ int rv;
+
+ rv = clk_set_parent_by_clk(sc->clk_hdmi, sc->clk_parent);
+ if (rv != 0) {
+ device_printf(sc->dev,
+ "Cannot set parent for 'hdmi' clock\n");
+ return (rv);
+ }
+
+ /* 594 MHz is arbitrarily selected value */
+ rv = clk_set_freq(sc->clk_parent, 594000000, 0);
+ if (rv != 0) {
+ device_printf(sc->dev,
+ "Cannot set frequency for 'hdmi' parent clock\n");
+ return (rv);
+ }
+ rv = clk_set_freq(sc->clk_hdmi, 594000000 / 4, 0);
+ if (rv != 0) {
+ device_printf(sc->dev,
+ "Cannot set frequency for 'hdmi' parent clock\n");
+ return (rv);
+ }
+
+ rv = regulator_enable(sc->supply_hdmi);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot enable 'hdmi' regulator\n");
+ return (rv);
+ }
+ rv = regulator_enable(sc->supply_pll);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot enable 'pll' regulator\n");
+ return (rv);
+ }
+ rv = regulator_enable(sc->supply_vdd);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot enable 'vdd' regulator\n");
+ return (rv);
+ }
+
+ rv = clk_enable(sc->clk_hdmi);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot enable 'hdmi' clock\n");
+ return (rv);
+ }
+
+ rv = hwreset_deassert(sc->hwreset_hdmi);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot unreset 'hdmi' reset\n");
+ return (rv);
+ }
+ return (0);
+}
+
+static void
+hdmi_intr(void *arg)
+{
+ struct hdmi_softc *sc;
+ uint32_t status;
+
+ sc = arg;
+
+ /* Confirm interrupt */
+ status = RD4(sc, HDMI_NV_PDISP_INT_STATUS);
+ WR4(sc, HDMI_NV_PDISP_INT_STATUS, status);
+
+ /* process audio verb from HDA */
+ if (status & INT_CODEC_SCRATCH0)
+ hda_intr(sc);
+}
+
+static int
+hdmi_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
+ return (ENXIO);
+
+ device_set_desc(dev, "Tegra HDMI");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+hdmi_attach(device_t dev)
+{
+ struct hdmi_softc *sc;
+ phandle_t node;
+ int rid, rv;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ sc->output.dev = sc->dev;
+ node = ofw_bus_get_node(sc->dev);
+
+ sc->audio_src_type = SOURCE_SELECT_AUTO;
+ sc->audio_freq = 44100;
+ sc->audio_chans = 2;
+ sc->hdmi_mode = false;
+
+ sc->tmds_config = tegra124_tmds_config;
+ sc->n_tmds_configs = nitems(tegra124_tmds_config);
+
+ rid = 0;
+ sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->mem_res == NULL) {
+ device_printf(dev, "Cannot allocate memory resources\n");
+ goto fail;
+ }
+
+ rid = 0;
+ sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
+ if (sc->irq_res == NULL) {
+ device_printf(dev, "Cannot allocate IRQ resources\n");
+ goto fail;
+ }
+
+ rv = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
+ NULL, hdmi_intr, sc, &sc->irq_ih);
+ if (rv != 0) {
+ device_printf(dev,
+ "WARNING: unable to register interrupt handler\n");
+ goto fail;
+ }
+
+ rv = get_fdt_resources(sc, node);
+ if (rv != 0) {
+ device_printf(dev, "Cannot parse FDT resources\n");
+ goto fail;
+ }
+ rv = enable_fdt_resources(sc);
+ if (rv != 0) {
+ device_printf(dev, "Cannot enable FDT resources\n");
+ goto fail;
+ }
+
+ rv = TEGRA_DRM_REGISTER_CLIENT(device_get_parent(sc->dev), sc->dev);
+ if (rv != 0) {
+ device_printf(dev, "Cannot register DRM device\n");
+ goto fail;
+ }
+ return (bus_generic_attach(dev));
+
+fail:
+ TEGRA_DRM_DEREGISTER_CLIENT(device_get_parent(sc->dev), sc->dev);
+
+ if (sc->irq_ih != NULL)
+ bus_teardown_intr(dev, sc->irq_res, sc->irq_ih);
+ if (sc->clk_parent != NULL)
+ clk_release(sc->clk_parent);
+ if (sc->clk_hdmi != NULL)
+ clk_release(sc->clk_hdmi);
+ if (sc->hwreset_hdmi != NULL)
+ hwreset_release(sc->hwreset_hdmi);
+ if (sc->supply_hdmi != NULL)
+ regulator_release(sc->supply_hdmi);
+ if (sc->supply_pll != NULL)
+ regulator_release(sc->supply_pll);
+ if (sc->supply_vdd != NULL)
+ regulator_release(sc->supply_vdd);
+ if (sc->irq_res != NULL)
+ bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq_res);
+ if (sc->mem_res != NULL)
+ bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->mem_res);
+ return (ENXIO);
+}
+
+static int
+hdmi_detach(device_t dev)
+{
+ struct hdmi_softc *sc;
+ sc = device_get_softc(dev);
+
+ TEGRA_DRM_DEREGISTER_CLIENT(device_get_parent(sc->dev), sc->dev);
+
+ if (sc->irq_ih != NULL)
+ bus_teardown_intr(dev, sc->irq_res, sc->irq_ih);
+ if (sc->clk_parent != NULL)
+ clk_release(sc->clk_parent);
+ if (sc->clk_hdmi != NULL)
+ clk_release(sc->clk_hdmi);
+ if (sc->hwreset_hdmi != NULL)
+ hwreset_release(sc->hwreset_hdmi);
+ if (sc->supply_hdmi != NULL)
+ regulator_release(sc->supply_hdmi);
+ if (sc->supply_pll != NULL)
+ regulator_release(sc->supply_pll);
+ if (sc->supply_vdd != NULL)
+ regulator_release(sc->supply_vdd);
+ if (sc->irq_res != NULL)
+ bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq_res);
+ if (sc->mem_res != NULL)
+ bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->mem_res);
+ return (bus_generic_detach(dev));
+}
+
+static device_method_t tegra_hdmi_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, hdmi_probe),
+ DEVMETHOD(device_attach, hdmi_attach),
+ DEVMETHOD(device_detach, hdmi_detach),
+
+ /* tegra drm interface */
+ DEVMETHOD(tegra_drm_init_client, hdmi_init_client),
+ DEVMETHOD(tegra_drm_exit_client, hdmi_exit_client),
+
+ DEVMETHOD_END
+};
+
+static devclass_t tegra_hdmi_devclass;
+DEFINE_CLASS_0(tegra_hdmi, tegra_hdmi_driver, tegra_hdmi_methods,
+ sizeof(struct hdmi_softc));
+DRIVER_MODULE(tegra_hdmi, host1x, tegra_hdmi_driver,
+tegra_hdmi_devclass, 0, 0);
diff --git a/sys/arm/nvidia/drm2/tegra_hdmi_reg.h b/sys/arm/nvidia/drm2/tegra_hdmi_reg.h
new file mode 100644
index 000000000000..091eb2386773
--- /dev/null
+++ b/sys/arm/nvidia/drm2/tegra_hdmi_reg.h
@@ -0,0 +1,283 @@
+/*-
+ * Copyright 1992-2016 Michal Meloun
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _TEGRA_HDMI_REG_H_
+#define _TEGRA_HDMI_REG_H_
+
+/*
+ * !!! WARNING !!!
+ * Tegra manual uses registers index (and not register addreses).
+ * We follow the TRM notation and index is converted to offset in
+ * WR4 / RD4 macros
+ */
+#define HDMI_NV_PDISP_SOR_STATE0 0x001
+#define SOR_STATE0_UPDATE (1 << 0)
+
+#define HDMI_NV_PDISP_SOR_STATE1 0x002
+#define SOR_STATE1_ATTACHED (1 << 3)
+#define SOR_STATE1_ASY_ORMODE_NORMAL (1 << 2)
+#define SOR_STATE1_ASY_HEAD_OPMODE(x) (((x) & 0x3) << 0)
+#define ASY_HEAD_OPMODE_SLEEP 0
+#define ASY_HEAD_OPMODE_SNOOZE 1
+#define ASY_HEAD_OPMODE_AWAKE 2
+
+#define HDMI_NV_PDISP_SOR_STATE2 0x003
+#define SOR_STATE2_ASY_DEPOL_NEG (1 << 14)
+#define SOR_STATE2_ASY_VSYNCPOL_NEG (1 << 13)
+#define SOR_STATE2_ASY_HSYNCPOL_NEG (1 << 12)
+#define SOR_STATE2_ASY_PROTOCOL(x) (((x) & 0xf) << 8)
+#define ASY_PROTOCOL_SINGLE_TMDS_A 1
+#define ASY_PROTOCOL_CUSTOM 15
+#define SOR_STATE2_ASY_CRCMODE(x) (((x) & 0x3) << 6)
+#define ASY_CRCMODE_ACTIVE 0
+#define ASY_CRCMODE_COMPLETE 1
+#define ASY_CRCMODE_NON_ACTIVE 2
+#define SOR_STATE2_ASY_SUBOWNER(x) (((x) & 0x3) << 4)
+#define ASY_SUBOWNER_NONE 0
+#define ASY_SUBOWNER_SUBHEAD0 1
+#define ASY_SUBOWNER_SUBHEAD1 2
+#define SUBOWNER_BOTH 3
+#define SOR_STATE2_ASY_OWNER(x) (((x) & 0x3) << 0)
+#define ASY_OWNER_NONE 0
+#define ASY_OWNER_HEAD0 1
+
+#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL 0x01e
+#define AUDIO_INFOFRAME_CTRL_ENABLE (1 << 0)
+#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_STATUS 0x01f
+#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_HEADER 0x020
+#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_LOW 0x021
+#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_HIGH 0x022
+#define INFOFRAME_HEADER_LEN(x) (((x) & 0x0f) << 16)
+#define INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) << 8)
+#define INFOFRAME_HEADER_TYPE(x) (((x) & 0xff) << 0)
+
+#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL 0x023
+#define AVI_INFOFRAME_CTRL_ENABLE (1 << 0)
+#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_STATUS 0x024
+#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_HEADER 0x025
+#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_LOW 0x026
+#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_HIGH 0x027
+#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_LOW 0x028
+#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_HIGH 0x029
+
+#define HDMI_NV_PDISP_HDMI_GENERIC_CTRL 0x02a
+#define GENERIC_CTRL_AUDIO (1 << 16)
+#define GENERIC_CTRL_HBLANK (1 << 12)
+#define GENERIC_CTRL_SINGLE (1 << 8)
+#define GENERIC_CTRL_OTHER (1 << 4)
+#define GENERIC_CTRL_ENABLE (1 << 0)
+#define HDMI_NV_PDISP_HDMI_GENERIC_STATUS 0x02b
+#define HDMI_NV_PDISP_HDMI_GENERIC_HEADER 0x02c
+#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK0_LOW 0x02d
+#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK0_HIGH 0x02e
+#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK1_LOW 0x02f
+#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK1_HIGH 0x030
+#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK2_LOW 0x031
+#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK2_HIGH 0x032
+#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK3_LOW 0x033
+#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK3_HIGH 0x034
+
+#define HDMI_NV_PDISP_HDMI_ACR_CTRL 0x035
+#define HDMI_NV_PDISP_HDMI_ACR_0320_SUBPACK_LOW 0x036
+#define HDMI_NV_PDISP_HDMI_ACR_0320_SUBPACK_HIGH 0x037
+#define HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW 0x038
+#define HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH 0x039
+#define HDMI_NV_PDISP_HDMI_ACR_0882_SUBPACK_LOW 0x03a
+#define HDMI_NV_PDISP_HDMI_ACR_0882_SUBPACK_HIGH 0x03b
+#define HDMI_NV_PDISP_HDMI_ACR_1764_SUBPACK_LOW 0x03c
+#define HDMI_NV_PDISP_HDMI_ACR_1764_SUBPACK_HIGH 0x03d
+#define HDMI_NV_PDISP_HDMI_ACR_0480_SUBPACK_LOW 0x03e
+#define HDMI_NV_PDISP_HDMI_ACR_0480_SUBPACK_HIGH 0x03f
+#define HDMI_NV_PDISP_HDMI_ACR_0960_SUBPACK_LOW 0x040
+#define HDMI_NV_PDISP_HDMI_ACR_0960_SUBPACK_HIGH 0x041
+#define HDMI_NV_PDISP_HDMI_ACR_1920_SUBPACK_LOW 0x042
+#define HDMI_NV_PDISP_HDMI_ACR_1920_SUBPACK_HIGH 0x043
+#define ACR_ENABLE (1U << 31)
+#define ACR_SUBPACK_CTS(x) (((x) & 0xffffff) << 8)
+#define ACR_SUBPACK_N(x) (((x) & 0xffffff) << 0)
+
+#define HDMI_NV_PDISP_HDMI_CTRL 0x044
+#define HDMI_CTRL_ENABLE (1 << 30)
+#define HDMI_CTRL_CA_SELECT (1 << 28)
+#define HDMI_CTRL_SS_SELECT (1 << 27)
+#define HDMI_CTRL_SF_SELECT (1 << 26)
+#define HDMI_CTRL_CC_SELECT (1 << 25)
+#define HDMI_CTRL_CT_SELECT (1 << 24)
+#define HDMI_CTRL_MAX_AC_PACKET(x) (((x) & 0x1f) << 16)
+#define HDMI_CTRL_SAMPLE_FLAT (1 << 12)
+#define HDMI_CTRL_AUDIO_LAYOUT_SELECT (1 << 10)
+#define HDMI_CTRL_AUDIO_LAYOUT (1 << 8)
+#define HDMI_CTRL_REKEY(x) (((x) & 0x7f) << 0)
+
+#define HDMI_NV_PDISP_HDMI_VSYNC_WINDOW 0x046
+#define VSYNC_WINDOW_ENABLE (1U << 31)
+#define VSYNC_WINDOW_START(x) (((x) & 0x3ff) << 16)
+#define VSYNC_WINDOW_END(x) (((x) & 0x3ff) << 0)
+
+#define HDMI_NV_PDISP_HDMI_SPARE 0x04f
+#define SPARE_ACR_PRIORITY (1U << 31)
+#define SPARE_CTS_RESET_VAL(x) (((x) & 0x7) << 16)
+#define SPARE_SUPRESS_SP_B (1 << 2)
+#define SPARE_FORCE_SW_CTS (1 << 1)
+#define SPARE_HW_CTS (1 << 0)
+
+#define HDMI_NV_PDISP_SOR_PWR 0x055
+#define SOR_PWR_SETTING_NEW (1U << 31)
+#define SOR_PWR_SAFE_STATE_PU (1 << 16)
+#define SOR_PWR_NORMAL_START_ALT (1 << 1)
+#define SOR_PWR_NORMAL_STATE_PU (1 << 0)
+
+#define HDMI_NV_PDISP_SOR_PLL0 0x057
+#define SOR_PLL0_TX_REG_LOAD(x) (((x) & 0xf) << 28)
+#define SOR_PLL0_ICHPMP(x) (((x) & 0xf) << 24)
+#define SOR_PLL0_FILTER(x) (((x) & 0xf) << 16)
+#define SOR_PLL0_BG_V17_S(x) (((x) & 0xf) << 12)
+#define SOR_PLL0_VCOCAP(x) (((x) & 0xf) << 8)
+#define SOR_PLL0_PULLDOWN (1 << 5)
+#define SOR_PLL0_RESISTORSEL (1 << 4)
+#define SOR_PLL0_PDPORT (1 << 3)
+#define SOR_PLL0_VCOPD (1 << 2)
+#define SOR_PLL0_PDBG (1 << 1)
+#define SOR_PLL0_PWR (1 << 0)
+
+#define HDMI_NV_PDISP_SOR_PLL1 0x058
+#define SOR_PLL1_S_D_PIN_PE (1 << 30)
+#define SOR_PLL1_HALF_FULL_PE (1 << 29)
+#define SOR_PLL1_PE_EN (1 << 28)
+#define SOR_PLL1_LOADADJ(x) (((x) & 0xf) << 20)
+#define SOR_PLL1_TMDS_TERMADJ(x) (((x) & 0xf) << 9)
+#define SOR_PLL1_TMDS_TERM (1 << 8)
+
+#define HDMI_NV_PDISP_SOR_CSTM 0x05a
+#define SOR_CSTM_ROTAT(x) (((x) & 0xf) << 28)
+#define SOR_CSTM_ROTCLK(x) (((x) & 0xf) << 24)
+#define SOR_CSTM_PLLDIV (1 << 21)
+#define SOR_CSTM_BALANCED (1 << 19)
+#define SOR_CSTM_NEW_MODE (1 << 18)
+#define SOR_CSTM_DUP_SYNC (1 << 17)
+#define SOR_CSTM_LVDS_ENABLE (1 << 16)
+#define SOR_CSTM_LINKACTB (1 << 15)
+#define SOR_CSTM_LINKACTA (1 << 14)
+#define SOR_CSTM_MODE(x) (((x) & 0x3) << 12)
+#define CSTM_MODE_LVDS 0
+#define CSTM_MODE_TMDS 1
+
+#define HDMI_NV_PDISP_SOR_SEQ_CTL 0x05f
+#define SOR_SEQ_SWITCH (1 << 30)
+#define SOR_SEQ_STATUS (1 << 28)
+#define SOR_SEQ_PC(x) (((x) & 0xf) << 16)
+#define SOR_SEQ_PD_PC_ALT(x) (((x) & 0xf) << 12)
+#define SOR_SEQ_PD_PC(x) (((x) & 0xf) << 8)
+#define SOR_SEQ_PU_PC_ALT(x) (((x) & 0xf) << 4)
+#define SOR_SEQ_PU_PC(x) (((x) & 0xf) << 0)
+
+#define HDMI_NV_PDISP_SOR_SEQ_INST(x) (0x060 + (x))
+#define SOR_SEQ_INST_PLL_PULLDOWN (1U << 31)
+#define SOR_SEQ_INST_POWERDOWN_MACRO (1 << 30)
+#define SOR_SEQ_INST_ASSERT_PLL_RESETV (1 << 29)
+#define SOR_SEQ_INST_BLANK_V (1 << 28)
+#define SOR_SEQ_INST_BLANK_H (1 << 27)
+#define SOR_SEQ_INST_BLANK_DE (1 << 26)
+#define SOR_SEQ_INST_BLACK_DATA (1 << 25)
+#define SOR_SEQ_INST_TRISTATE_IOS (1 << 24)
+#define SOR_SEQ_INST_DRIVE_PWM_OUT_LO (1 << 23)
+#define SOR_SEQ_INST_PIN_B_HIGH (1 << 22)
+#define SOR_SEQ_INST_PIN_A_HIGH (1 << 21)
+#define SOR_SEQ_INST_HALT (1 << 15)
+#define SOR_SEQ_INST_WAIT_UNITS(x) (((x) & 0x3) << 12)
+#define WAIT_UNITS_US 0
+#define WAIT_UNITS_MS 1
+#define WAIT_UNITS_VSYNC 2
+#define SOR_SEQ_INST_WAIT_TIME(x) (((x) & 0x3ff) << 0)
+
+#define HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT 0x07e
+
+#define HDMI_NV_PDISP_AUDIO_N 0x08c
+#define AUDIO_N_LOOKUP (1 << 28)
+#define AUDIO_N_GENERATE_ALTERNATE (1 << 24)
+#define AUDIO_N_RESETF (1 << 20)
+#define AUDIO_N_VALUE(x) (((x) & 0xfffff) << 0)
+
+#define HDMI_NV_PDISP_SOR_REFCLK 0x095
+#define SOR_REFCLK_DIV_INT(x) (((x) & 0xff) << 8)
+#define SOR_REFCLK_DIV_FRAC(x) (((x) & 0x03) << 6)
+
+#define HDMI_NV_PDISP_INPUT_CONTROL 0x097
+#define ARM_VIDEO_RANGE_LIMITED (1 << 1)
+#define HDMI_SRC_DISPLAYB (1 << 0)
+
+#define HDMI_NV_PDISP_PE_CURRENT 0x099
+#define HDMI_NV_PDISP_SOR_AUDIO_CNTRL0 0x0ac
+#define SOR_AUDIO_CNTRL0_INJECT_NULLSMPL (1 << 29)
+#define SOR_AUDIO_CNTRL0_SOURCE_SELECT(x) (((x) & 0x03) << 20)
+#define SOURCE_SELECT_AUTO 0
+#define SOURCE_SELECT_SPDIF 1
+#define SOURCE_SELECT_HDAL 2
+#define SOR_AUDIO_CNTRL0_AFIFO_FLUSH (1 << 12)
+
+#define HDMI_NV_PDISP_SOR_AUDIO_SPARE0 0x0ae
+#define SOR_AUDIO_SPARE0_HBR_ENABLE (1 << 27)
+
+#define HDMI_NV_PDISP_SOR_AUDIO_NVAL_0320 0x0af
+#define HDMI_NV_PDISP_SOR_AUDIO_NVAL_0441 0x0b0
+#define HDMI_NV_PDISP_SOR_AUDIO_NVAL_0882 0x0b1
+#define HDMI_NV_PDISP_SOR_AUDIO_NVAL_1764 0x0b2
+#define HDMI_NV_PDISP_SOR_AUDIO_NVAL_0480 0x0b3
+#define HDMI_NV_PDISP_SOR_AUDIO_NVAL_0960 0x0b4
+#define HDMI_NV_PDISP_SOR_AUDIO_NVAL_1920 0x0b5
+#define HDMI_NV_PDISP_SOR_AUDIO_HDA_SCRATCH0 0x0b6
+#define HDMI_NV_PDISP_SOR_AUDIO_HDA_SCRATCH1 0x0b7
+#define HDMI_NV_PDISP_SOR_AUDIO_HDA_SCRATCH2 0x0b8
+#define HDMI_NV_PDISP_SOR_AUDIO_HDA_SCRATCH3 0x0b9
+#define HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0 0x0ba
+#define HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH1 0x0bb
+#define HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR 0x0bc
+#define HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE 0x0bd
+#define SOR_AUDIO_HDA_PRESENSE_VALID (1 << 1)
+#define SOR_AUDIO_HDA_PRESENSE_PRESENT (1 << 0)
+
+#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0320 0x0bf
+#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0441 0x0c0
+#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0882 0x0c1
+#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_1764 0x0c2
+#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0480 0x0c3
+#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0960 0x0c4
+#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_1920 0x0c5
+#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_DEFAULT 0x0c6
+
+#define HDMI_NV_PDISP_INT_STATUS 0x0cc
+#define INT_SCRATCH (1 << 3)
+#define INT_CP_REQUEST (1 << 2)
+#define INT_CODEC_SCRATCH1 (1 << 1)
+#define INT_CODEC_SCRATCH0 (1 << 0)
+
+#define HDMI_NV_PDISP_INT_MASK 0x0cd
+#define HDMI_NV_PDISP_INT_ENABLE 0x0ce
+#define HDMI_NV_PDISP_SOR_IO_PEAK_CURRENT 0x0d1
+#define HDMI_NV_PDISP_SOR_PAD_CTLS0 0x0d2
+
+#endif /* _TEGRA_HDMI_REG_H_ */
diff --git a/sys/arm/nvidia/drm2/tegra_host1x.c b/sys/arm/nvidia/drm2/tegra_host1x.c
new file mode 100644
index 000000000000..6d76beb83cd5
--- /dev/null
+++ b/sys/arm/nvidia/drm2/tegra_host1x.c
@@ -0,0 +1,645 @@
+/*-
+ * Copyright (c) 2015 Michal Meloun
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/clock.h>
+#include <sys/kernel.h>
+#include <sys/limits.h>
+#include <sys/lock.h>
+
+#include <sys/module.h>
+#include <sys/resource.h>
+#include <sys/sx.h>
+#include <sys/rman.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+#include <dev/drm2/drmP.h>
+#include <dev/drm2/drm_crtc_helper.h>
+#include <dev/drm2/drm_fb_helper.h>
+#include <dev/fdt/simplebus.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <arm/nvidia/drm2/tegra_drm.h>
+
+#include "fb_if.h"
+#include "tegra_drm_if.h"
+
+#define WR4(_sc, _r, _v) bus_rite_4((_sc)->mem_res, (_r), (_v))
+#define RD4(_sc, _r) bus_read_4((_sc)->mem_res, (_r))
+
+#define LOCK(_sc) sx_xlock(&(_sc)->lock)
+#define UNLOCK(_sc) sx_xunlock(&(_sc)->lock)
+#define SLEEP(_sc, timeout) sx_sleep(sc, &sc->lock, 0, "host1x", timeout);
+#define LOCK_INIT(_sc) sx_init(&_sc->lock, "host1x")
+#define LOCK_DESTROY(_sc) sx_destroy(&_sc->lock)
+#define ASSERT_LOCKED(_sc) sx_assert(&_sc->lock, SA_LOCKED)
+#define ASSERT_UNLOCKED(_sc) sx_assert(&_sc->lock, SA_UNLOCKED)
+
+static struct ofw_compat_data compat_data[] = {
+ {"nvidia,tegra124-host1x", 1},
+ {NULL, 0}
+};
+
+#define DRIVER_NAME "tegra"
+#define DRIVER_DESC "NVIDIA Tegra TK1"
+#define DRIVER_DATE "20151101"
+#define DRIVER_MAJOR 0
+#define DRIVER_MINOR 0
+#define DRIVER_PATCHLEVEL 0
+
+struct client_info;
+TAILQ_HEAD(client_list, client_info);
+typedef struct client_list client_list_t;
+
+struct client_info {
+ TAILQ_ENTRY(client_info) list_e;
+ device_t client;
+ int activated;
+};
+
+struct host1x_softc {
+ struct simplebus_softc simplebus_sc; /* must be first */
+ device_t dev;
+ struct sx lock;
+ int attach_done;
+
+ struct resource *mem_res;
+ struct resource *syncpt_irq_res;
+ void *syncpt_irq_h;
+ struct resource *gen_irq_res;
+ void *gen_irq_h;
+
+ clk_t clk;
+ hwreset_t reset;
+ struct intr_config_hook irq_hook;
+
+ int drm_inited;
+ client_list_t clients;
+
+ struct tegra_drm *tegra_drm;
+};
+
+static void
+host1x_output_poll_changed(struct drm_device *drm_dev)
+{
+ struct tegra_drm *drm;
+
+ drm = container_of(drm_dev, struct tegra_drm, drm_dev);
+ if (drm->fb != NULL)
+ drm_fb_helper_hotplug_event(&drm->fb->fb_helper);
+}
+
+static const struct drm_mode_config_funcs mode_config_funcs = {
+ .fb_create = tegra_drm_fb_create,
+ .output_poll_changed = host1x_output_poll_changed,
+};
+
+static int
+host1x_drm_init(struct host1x_softc *sc)
+{
+ struct client_info *entry;
+ int rv;
+
+ LOCK(sc);
+
+ TAILQ_FOREACH(entry, &sc->clients, list_e) {
+ if (entry->activated)
+ continue;
+ rv = TEGRA_DRM_INIT_CLIENT(entry->client, sc->dev,
+ sc->tegra_drm);
+ if (rv != 0) {
+ device_printf(sc->dev,
+ "Cannot init DRM client %s: %d\n",
+ device_get_name(entry->client), rv);
+ return (rv);
+ }
+ entry->activated = 1;
+ }
+ UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+host1x_drm_exit(struct host1x_softc *sc)
+{
+ struct client_info *entry;
+ int rv;
+#ifdef FREEBSD_NOTYET
+ struct drm_device *dev, *tmp;
+#endif
+ LOCK(sc);
+ if (!sc->drm_inited) {
+ UNLOCK(sc);
+ return (0);
+ }
+ TAILQ_FOREACH_REVERSE(entry, &sc->clients, client_list, list_e) {
+ if (!entry->activated)
+ continue;
+ rv = TEGRA_DRM_EXIT_CLIENT(entry->client, sc->dev,
+ sc->tegra_drm);
+ if (rv != 0) {
+ device_printf(sc->dev,
+ "Cannot exit DRM client %s: %d\n",
+ device_get_name(entry->client), rv);
+ }
+ entry->activated = 0;
+ }
+
+#ifdef FREEBSD_NOTYET
+ list_for_each_entry_safe(dev, tmp, &driver->device_list, driver_item)
+ drm_put_dev(dev);
+#endif
+ sc->drm_inited = 0;
+ UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+host1x_drm_load(struct drm_device *drm_dev, unsigned long flags)
+{
+ struct host1x_softc *sc;
+ int rv;
+
+ sc = device_get_softc(drm_dev->dev);
+
+ drm_mode_config_init(drm_dev);
+ drm_dev->mode_config.min_width = 32;
+ drm_dev->mode_config.min_height = 32;
+ drm_dev->mode_config.max_width = 4096;
+ drm_dev->mode_config.max_height = 4096;
+ drm_dev->mode_config.funcs = &mode_config_funcs;
+
+ rv = host1x_drm_init(sc);
+ if (rv != 0)
+ goto fail_host1x;
+
+ drm_dev->irq_enabled = true;
+ drm_dev->max_vblank_count = 0xffffffff;
+ drm_dev->vblank_disable_allowed = true;
+
+ rv = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc);
+ if (rv != 0)
+ goto fail_vblank;
+
+ drm_mode_config_reset(drm_dev);
+
+ rv = tegra_drm_fb_init(drm_dev);
+ if (rv != 0)
+ goto fail_fb;
+ drm_kms_helper_poll_init(drm_dev);
+
+ return (0);
+
+fail_fb:
+ tegra_drm_fb_destroy(drm_dev);
+ drm_vblank_cleanup(drm_dev);
+fail_vblank:
+ host1x_drm_exit(sc);
+fail_host1x:
+ drm_mode_config_cleanup(drm_dev);
+
+ return (rv);
+}
+
+static int
+host1x_drm_unload(struct drm_device *drm_dev)
+{
+ struct host1x_softc *sc;
+ int rv;
+
+ sc = device_get_softc(drm_dev->dev);
+
+ drm_kms_helper_poll_fini(drm_dev);
+ tegra_drm_fb_destroy(drm_dev);
+ drm_mode_config_cleanup(drm_dev);
+
+ rv = host1x_drm_exit(sc);
+ if (rv < 0)
+ return (rv);
+ return (0);
+}
+
+static int
+host1x_drm_open(struct drm_device *drm_dev, struct drm_file *filp)
+{
+
+ return (0);
+}
+
+static void
+tegra_drm_preclose(struct drm_device *drm, struct drm_file *file)
+{
+ struct drm_crtc *crtc;
+
+ list_for_each_entry(crtc, &drm->mode_config.crtc_list, head)
+ tegra_dc_cancel_page_flip(crtc, file);
+}
+
+static void
+host1x_drm_lastclose(struct drm_device *drm_dev)
+{
+
+ struct tegra_drm *drm;
+
+ drm = container_of(drm_dev, struct tegra_drm, drm_dev);
+ if (drm->fb != NULL)
+ drm_fb_helper_restore_fbdev_mode(&drm->fb->fb_helper);
+}
+
+static int
+host1x_drm_enable_vblank(struct drm_device *drm_dev, int pipe)
+{
+ struct drm_crtc *crtc;
+
+ list_for_each_entry(crtc, &drm_dev->mode_config.crtc_list, head) {
+ if (pipe == tegra_dc_get_pipe(crtc)) {
+ tegra_dc_enable_vblank(crtc);
+ return (0);
+ }
+ }
+ return (-ENODEV);
+}
+
+static void
+host1x_drm_disable_vblank(struct drm_device *drm_dev, int pipe)
+{
+ struct drm_crtc *crtc;
+
+ list_for_each_entry(crtc, &drm_dev->mode_config.crtc_list, head) {
+ if (pipe == tegra_dc_get_pipe(crtc)) {
+ tegra_dc_disable_vblank(crtc);
+ return;
+ }
+ }
+}
+
+static struct drm_ioctl_desc host1x_drm_ioctls[] = {
+};
+
+struct drm_driver tegra_drm_driver = {
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME,
+ .load = host1x_drm_load,
+ .unload = host1x_drm_unload,
+ .open = host1x_drm_open,
+ .preclose = tegra_drm_preclose,
+ .lastclose = host1x_drm_lastclose,
+
+ .get_vblank_counter = drm_vblank_count,
+ .enable_vblank = host1x_drm_enable_vblank,
+ .disable_vblank = host1x_drm_disable_vblank,
+
+ /* Fields filled by tegra_bo_driver_register()
+ .gem_free_object
+ .gem_pager_ops
+ .dumb_create
+ .dumb_map_offset
+ .dumb_destroy
+ */
+ .ioctls = host1x_drm_ioctls,
+ .num_ioctls = nitems(host1x_drm_ioctls),
+
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .date = DRIVER_DATE,
+ .major = DRIVER_MAJOR,
+ .minor = DRIVER_MINOR,
+ .patchlevel = DRIVER_PATCHLEVEL,
+};
+
+/*
+ * ----------------- Device methods -------------------------
+ */
+static void
+host1x_irq_hook(void *arg)
+{
+ struct host1x_softc *sc;
+ int rv;
+
+ sc = arg;
+ config_intrhook_disestablish(&sc->irq_hook);
+
+ tegra_bo_driver_register(&tegra_drm_driver);
+ rv = drm_get_platform_dev(sc->dev, &sc->tegra_drm->drm_dev,
+ &tegra_drm_driver);
+ if (rv != 0) {
+ device_printf(sc->dev, "drm_get_platform_dev(): %d\n", rv);
+ return;
+ }
+
+ sc->drm_inited = 1;
+}
+
+static struct fb_info *
+host1x_fb_helper_getinfo(device_t dev)
+{
+ struct host1x_softc *sc;
+
+ sc = device_get_softc(dev);
+ if (sc->tegra_drm == NULL)
+ return (NULL);
+ return (tegra_drm_fb_getinfo(&sc->tegra_drm->drm_dev));
+}
+
+static int
+host1x_register_client(device_t dev, device_t client)
+{
+ struct host1x_softc *sc;
+ struct client_info *entry;
+
+ sc = device_get_softc(dev);
+
+ entry = malloc(sizeof(struct client_info), M_DEVBUF, M_WAITOK | M_ZERO);
+ entry->client = client;
+ entry->activated = 0;
+
+ LOCK(sc);
+ TAILQ_INSERT_TAIL(&sc->clients, entry, list_e);
+ UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+host1x_deregister_client(device_t dev, device_t client)
+{
+ struct host1x_softc *sc;
+ struct client_info *entry;
+
+ sc = device_get_softc(dev);
+
+ LOCK(sc);
+ TAILQ_FOREACH(entry, &sc->clients, list_e) {
+ if (entry->client == client) {
+ if (entry->activated)
+ panic("Tegra DRM: Attempt to deregister "
+ "activated client");
+ TAILQ_REMOVE(&sc->clients, entry, list_e);
+ free(entry, M_DEVBUF);
+ UNLOCK(sc);
+ return (0);
+ }
+ }
+ UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+host1x_gen_intr(void *arg)
+{
+ struct host1x_softc *sc;
+
+ sc = (struct host1x_softc *)arg;
+ LOCK(sc);
+ UNLOCK(sc);
+}
+
+static void
+host1x_syncpt_intr(void *arg)
+{
+ struct host1x_softc *sc;
+
+ sc = (struct host1x_softc *)arg;
+ LOCK(sc);
+ UNLOCK(sc);
+}
+
+static void
+host1x_new_pass(device_t dev)
+{
+ struct host1x_softc *sc;
+ int rv, rid;
+ phandle_t node;
+
+ /*
+ * We attach during BUS_PASS_BUS (because we must overcome simplebus),
+ * but some of our FDT resources are not ready until BUS_PASS_DEFAULT
+ */
+ sc = device_get_softc(dev);
+ if (sc->attach_done || bus_current_pass < BUS_PASS_DEFAULT) {
+ bus_generic_new_pass(dev);
+ return;
+ }
+
+ sc->attach_done = 1;
+ node = ofw_bus_get_node(dev);
+
+ /* Allocate our IRQ resource. */
+ rid = 0;
+ sc->syncpt_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+ RF_ACTIVE);
+ if (sc->syncpt_irq_res == NULL) {
+ device_printf(dev, "Cannot allocate interrupt.\n");
+ rv = ENXIO;
+ goto fail;
+ }
+ rid = 1;
+ sc->gen_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+ RF_ACTIVE);
+ if (sc->gen_irq_res == NULL) {
+ device_printf(dev, "Cannot allocate interrupt.\n");
+ rv = ENXIO;
+ goto fail;
+ }
+
+ /* FDT resources */
+ rv = hwreset_get_by_ofw_name(sc->dev, 0, "host1x", &sc->reset);
+ if (rv != 0) {
+ device_printf(dev, "Cannot get fuse reset\n");
+ goto fail;
+ }
+ rv = clk_get_by_ofw_index(sc->dev, 0, 0, &sc->clk);
+ if (rv != 0) {
+ device_printf(dev, "Cannot get i2c clock: %d\n", rv);
+ goto fail;
+ }
+
+ rv = clk_enable(sc->clk);
+ if (rv != 0) {
+ device_printf(dev, "Cannot enable clock: %d\n", rv);
+ goto fail;
+ }
+ rv = hwreset_deassert(sc->reset);
+ if (rv != 0) {
+ device_printf(sc->dev, "Cannot clear reset\n");
+ goto fail;
+ }
+
+ /* Setup interrupts */
+ rv = bus_setup_intr(dev, sc->gen_irq_res,
+ INTR_TYPE_MISC | INTR_MPSAFE, NULL, host1x_gen_intr,
+ sc, &sc->gen_irq_h);
+ if (rv) {
+ device_printf(dev, "Cannot setup gen interrupt.\n");
+ goto fail;
+ }
+
+ rv = bus_setup_intr(dev, sc->syncpt_irq_res,
+ INTR_TYPE_MISC | INTR_MPSAFE, NULL, host1x_syncpt_intr,
+ sc, &sc->syncpt_irq_h);
+ if (rv) {
+ device_printf(dev, "Cannot setup syncpt interrupt.\n");
+ goto fail;
+ }
+
+ simplebus_init(dev, 0);
+ for (node = OF_child(node); node > 0; node = OF_peer(node))
+ simplebus_add_device(dev, node, 0, NULL, -1, NULL);
+
+ sc->irq_hook.ich_func = host1x_irq_hook;
+ sc->irq_hook.ich_arg = sc;
+ config_intrhook_establish(&sc->irq_hook);
+ bus_generic_new_pass(dev);
+ return;
+
+fail:
+ device_detach(dev);
+ return;
+}
+
+static int
+host1x_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
+ return (ENXIO);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+host1x_attach(device_t dev)
+{
+ int rv, rid;
+ struct host1x_softc *sc;
+
+ sc = device_get_softc(dev);
+ sc->tegra_drm = malloc(sizeof(struct tegra_drm), DRM_MEM_DRIVER,
+ M_WAITOK | M_ZERO);
+
+ /* crosslink together all worlds */
+ sc->dev = dev;
+ sc->tegra_drm->drm_dev.dev_private = &sc->tegra_drm;
+ sc->tegra_drm->drm_dev.dev = dev;
+
+ TAILQ_INIT(&sc->clients);
+
+ LOCK_INIT(sc);
+
+ /* Get the memory resource for the register mapping. */
+ rid = 0;
+ sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->mem_res == NULL) {
+ device_printf(dev, "Cannot map registers.\n");
+ rv = ENXIO;
+ goto fail;
+ }
+
+ return (bus_generic_attach(dev));
+
+fail:
+ if (sc->tegra_drm != NULL)
+ free(sc->tegra_drm, DRM_MEM_DRIVER);
+ if (sc->mem_res != NULL)
+ bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->mem_res);
+ LOCK_DESTROY(sc);
+ return (rv);
+}
+
+static int
+host1x_detach(device_t dev)
+{
+ struct host1x_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ host1x_drm_exit(sc);
+
+ if (sc->gen_irq_h != NULL)
+ bus_teardown_intr(dev, sc->gen_irq_res, sc->gen_irq_h);
+ if (sc->tegra_drm != NULL)
+ free(sc->tegra_drm, DRM_MEM_DRIVER);
+ if (sc->clk != NULL)
+ clk_release(sc->clk);
+ if (sc->reset != NULL)
+ hwreset_release(sc->reset);
+ if (sc->syncpt_irq_h != NULL)
+ bus_teardown_intr(dev, sc->syncpt_irq_res, sc->syncpt_irq_h);
+ if (sc->gen_irq_res != NULL)
+ bus_release_resource(dev, SYS_RES_IRQ, 1, sc->gen_irq_res);
+ if (sc->syncpt_irq_res != NULL)
+ bus_release_resource(dev, SYS_RES_IRQ, 0, sc->syncpt_irq_res);
+ if (sc->mem_res != NULL)
+ bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->mem_res);
+ LOCK_DESTROY(sc);
+ return (bus_generic_detach(dev));
+}
+
+static device_method_t host1x_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, host1x_probe),
+ DEVMETHOD(device_attach, host1x_attach),
+ DEVMETHOD(device_detach, host1x_detach),
+
+ /* Bus interface */
+ DEVMETHOD(bus_new_pass, host1x_new_pass),
+
+ /* Framebuffer service methods */
+ DEVMETHOD(fb_getinfo, host1x_fb_helper_getinfo),
+
+ /* tegra drm interface */
+ DEVMETHOD(tegra_drm_register_client, host1x_register_client),
+ DEVMETHOD(tegra_drm_deregister_client, host1x_deregister_client),
+
+ DEVMETHOD_END
+};
+
+static devclass_t host1x_devclass;
+DEFINE_CLASS_1(host1x, host1x_driver, host1x_methods,
+ sizeof(struct host1x_softc), simplebus_driver);
+EARLY_DRIVER_MODULE(host1x, simplebus, host1x_driver,
+ host1x_devclass, 0, 0, BUS_PASS_BUS);
+
+/* Bindings for fbd device. */
+extern devclass_t fbd_devclass;
+extern driver_t fbd_driver;
+DRIVER_MODULE(fbd, host1x, fbd_driver, fbd_devclass, 0, 0);