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 #