From fb9cbc277cdaafd616afc2fba265078eb2b05e38 Mon Sep 17 00:00:00 2001 From: mmel Date: Mon, 26 Dec 2016 14:36:05 +0000 Subject: [PATCH] Implement drivers for NVIDIA tegra124 display controller, HDMI source and host1x module. Unfortunately, tegra124 SoC doesn't have 2D acceleration engine and 3D requires not yet started nouveau driver. These drivers forms a first non-x86 DRM2 enabled graphic stack. Note, there are 2 outstanding issues: - The code uses gross hack in order to be comply with OBJT_MGTDEVICE pager. (See tegra_bo_init_pager() in tegra_bo.c) - Due to improper(probably) refcounting in drm_gem_mmap_single() (in drm_gem.c), the gem objects are never released. I hope that I will be able to address both issues in finite time, but I don't want to touch x86 world now. MFC after: 1 month --- sys/arm/conf/TEGRA124 | 9 +- sys/arm/nvidia/drm2/hdmi.c | 1229 ++++++++++++++++++++ sys/arm/nvidia/drm2/hdmi.h | 335 ++++++ sys/arm/nvidia/drm2/tegra_bo.c | 369 ++++++ sys/arm/nvidia/drm2/tegra_dc.c | 1447 ++++++++++++++++++++++++ sys/arm/nvidia/drm2/tegra_dc_if.m | 57 + sys/arm/nvidia/drm2/tegra_dc_reg.h | 400 +++++++ sys/arm/nvidia/drm2/tegra_drm.h | 125 ++ sys/arm/nvidia/drm2/tegra_drm_if.m | 68 ++ sys/arm/nvidia/drm2/tegra_drm_subr.c | 177 +++ sys/arm/nvidia/drm2/tegra_fb.c | 338 ++++++ sys/arm/nvidia/drm2/tegra_hdmi.c | 1326 ++++++++++++++++++++++ sys/arm/nvidia/drm2/tegra_hdmi_reg.h | 285 +++++ sys/arm/nvidia/drm2/tegra_host1x.c | 650 +++++++++++ sys/arm/nvidia/tegra124/files.tegra124 | 18 +- 15 files changed, 6819 insertions(+), 14 deletions(-) create mode 100644 sys/arm/nvidia/drm2/hdmi.c create mode 100644 sys/arm/nvidia/drm2/hdmi.h create mode 100644 sys/arm/nvidia/drm2/tegra_bo.c create mode 100644 sys/arm/nvidia/drm2/tegra_dc.c create mode 100644 sys/arm/nvidia/drm2/tegra_dc_if.m create mode 100644 sys/arm/nvidia/drm2/tegra_dc_reg.h create mode 100644 sys/arm/nvidia/drm2/tegra_drm.h create mode 100644 sys/arm/nvidia/drm2/tegra_drm_if.m create mode 100644 sys/arm/nvidia/drm2/tegra_drm_subr.c create mode 100644 sys/arm/nvidia/drm2/tegra_fb.c create mode 100644 sys/arm/nvidia/drm2/tegra_hdmi.c create mode 100644 sys/arm/nvidia/drm2/tegra_hdmi_reg.h create mode 100644 sys/arm/nvidia/drm2/tegra_host1x.c diff --git a/sys/arm/conf/TEGRA124 b/sys/arm/conf/TEGRA124 index 19a6bbbcb5b6..eb60b2a2a6cf 100644 --- a/sys/arm/conf/TEGRA124 +++ b/sys/arm/conf/TEGRA124 @@ -123,11 +123,10 @@ device pci device re # RealTek 8139C+/8169/8169S/8110S # DRM2 -#device fbd -#device vt -#device splash -#device kbdmux -#device drm2 +device fbd +device vt +device kbdmux +device drm2 # Sound #device sound 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 +__FBSDID("$FreeBSD$"); + +#include +#include + +#include + +#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..27868e08b436 --- /dev/null +++ b/sys/arm/nvidia/drm2/hdmi.h @@ -0,0 +1,335 @@ +/* + * 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..251f683ff14e --- /dev/null +++ b/sys/arm/nvidia/drm2/tegra_bo.c @@ -0,0 +1,369 @@ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +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]; + cdev_pager_free_page(bo->cdev_pager, m); + vm_page_lock(m); + m->flags &= ~PG_FICTITIOUS; + vm_page_unwire(m, PQ_NONE); + vm_page_free(m); + vm_page_unlock(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; + 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..401f7cabaa4e --- /dev/null +++ b/sys/arm/nvidia/drm2/tegra_dc.c @@ -0,0 +1,1447 @@ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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(kernel_arena, + 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 + +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..7006eb71d74c --- /dev/null +++ b/sys/arm/nvidia/drm2/tegra_dc_reg.h @@ -0,0 +1,400 @@ +/*- + * 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..6485761e7224 --- /dev/null +++ b/sys/arm/nvidia/drm2/tegra_drm.h @@ -0,0 +1,125 @@ +/*- + * 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 + +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..18a72ec9dcaf --- /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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +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", 1, + (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); +} \ No newline at end of file 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +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..d55e0f0a778d --- /dev/null +++ b/sys/arm/nvidia/drm2/tegra_hdmi.c @@ -0,0 +1,1326 @@ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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..1fd387aa704f --- /dev/null +++ b/sys/arm/nvidia/drm2/tegra_hdmi_reg.h @@ -0,0 +1,285 @@ +/*- + * 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..b8271964211c --- /dev/null +++ b/sys/arm/nvidia/drm2/tegra_host1x.c @@ -0,0 +1,650 @@ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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); + diff --git a/sys/arm/nvidia/tegra124/files.tegra124 b/sys/arm/nvidia/tegra124/files.tegra124 index ac662f86bf8e..16a58cde470d 100644 --- a/sys/arm/nvidia/tegra124/files.tegra124 +++ b/sys/arm/nvidia/tegra124/files.tegra124 @@ -36,15 +36,15 @@ arm/nvidia/tegra_soctherm.c standard arm/nvidia/tegra_lic.c standard arm/nvidia/tegra_mc.c standard #arm/nvidia/tegra_hda.c optional snd_hda -#arm/nvidia/drm2/hdmi.c optional drm2 -#arm/nvidia/drm2/tegra_drm_if.m optional drm2 -#arm/nvidia/drm2/tegra_drm_subr.c optional drm2 -#arm/nvidia/drm2/tegra_host1x.c optional drm2 -#arm/nvidia/drm2/tegra_hdmi.c optional drm2 -#arm/nvidia/drm2/tegra_dc_if.m optional drm2 -#arm/nvidia/drm2/tegra_dc.c optional drm2 -#arm/nvidia/drm2/tegra_fb.c optional drm2 -#arm/nvidia/drm2/tegra_bo.c optional drm2 +arm/nvidia/drm2/hdmi.c optional drm2 +arm/nvidia/drm2/tegra_drm_if.m optional drm2 +arm/nvidia/drm2/tegra_drm_subr.c optional drm2 +arm/nvidia/drm2/tegra_host1x.c optional drm2 +arm/nvidia/drm2/tegra_hdmi.c optional drm2 +arm/nvidia/drm2/tegra_dc_if.m optional drm2 +arm/nvidia/drm2/tegra_dc.c optional drm2 +arm/nvidia/drm2/tegra_fb.c optional drm2 +arm/nvidia/drm2/tegra_bo.c optional drm2 # # Firmware #