1511 lines
40 KiB
C
1511 lines
40 KiB
C
/*
|
|
|
|
This file is provided under a dual BSD/GPLv2 license. When using or
|
|
redistributing this file, you may do so under either license.
|
|
|
|
GPL LICENSE SUMMARY
|
|
|
|
Copyright(c) 2011 Intel Corporation. All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of version 2 of the GNU General Public License as
|
|
published by the Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
|
|
The full GNU General Public License is included in this distribution
|
|
in the file called LICENSE.GPL.
|
|
|
|
Contact Information:
|
|
|
|
Intel Corporation
|
|
2200 Mission College Blvd.
|
|
Santa Clara, CA 95054
|
|
|
|
BSD LICENSE
|
|
|
|
Copyright(c) 2011 Intel Corporation. All rights reserved.
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* 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.
|
|
* Neither the name of Intel Corporation nor the names of its
|
|
contributors may be used to endorse or promote products derived
|
|
from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
|
|
OWNER 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 <linux/string.h>
|
|
#include <linux/types.h>
|
|
|
|
#include "otm_hdmi_types.h"
|
|
#include "edid_internal.h"
|
|
#include "hdmi_timings.h"
|
|
|
|
/*
|
|
* Macro for error checking
|
|
*/
|
|
#define VERIFY(exp, rc, error, label) \
|
|
if (!(exp)) { \
|
|
rc = error; \
|
|
goto label; \
|
|
}
|
|
|
|
#define VERIFY_QUICK(exp, label) \
|
|
if (!(exp)) { \
|
|
goto label; \
|
|
}
|
|
|
|
/*
|
|
* Various constants
|
|
*/
|
|
#define EDID_SIGNATURE 0x00FFFFFFFFFFFF00ull
|
|
|
|
/*
|
|
* Structure to keep state of read operation
|
|
*/
|
|
typedef struct {
|
|
unsigned char *buffer;
|
|
int position;
|
|
} read_context_t;
|
|
|
|
static void fetch_timing_descriptor(timing_descriptor_t *td,
|
|
read_context_t *rctx)
|
|
__attribute__((unused));
|
|
|
|
static void declare_mandatory_3d(edid_info_t *edid)
|
|
__attribute__((unused));
|
|
|
|
static void declare_short_3d(unsigned int struc_3d,
|
|
unsigned int mask,
|
|
edid_info_t *edid)
|
|
__attribute__((unused));
|
|
|
|
static void declare_explicit_3d(unsigned char *e,
|
|
unsigned int n,
|
|
edid_info_t *edid)
|
|
__attribute__((unused));
|
|
|
|
static void decode_misc_modes(unsigned char *e,
|
|
int n, edid_info_t *edid)
|
|
__attribute__((unused));
|
|
|
|
/*
|
|
* fetch_next_field()
|
|
*/
|
|
static void fetch_next_field(void *dst,
|
|
read_context_t *read_context,
|
|
unsigned int size)
|
|
{
|
|
unsigned char *b = read_context->buffer + read_context->position;
|
|
|
|
switch (size) {
|
|
#ifdef __ORDER_MSB__
|
|
case 1:
|
|
*(unsigned char *)dst = *b;
|
|
break;
|
|
|
|
case 2:
|
|
*(unsigned short *)dst |= (unsigned short)*(b + 1) << 0;
|
|
*(unsigned short *)dst |= (unsigned short)*(b + 0) << 8;
|
|
break;
|
|
|
|
case 4:
|
|
*(unsigned int *)dst |= (unsigned int)*(b + 3) << 0;
|
|
*(unsigned int *)dst |= (unsigned int)*(b + 2) << 8;
|
|
*(unsigned int *)dst |= (unsigned int)*(b + 1) << 16;
|
|
*(unsigned int *)dst |= (unsigned int)*(b + 0) << 24;
|
|
break;
|
|
|
|
case 8:
|
|
*(unsigned long long *)dst |= (unsigned long long)*(b + 7) << 0;
|
|
*(unsigned long long *)dst |= (unsigned long long)*(b + 6) << 8;
|
|
*(unsigned long long *)dst |=
|
|
(unsigned long long)*(b + 5) << 16;
|
|
*(unsigned long long *)dst |=
|
|
(unsigned long long)*(b + 4) << 24;
|
|
*(unsigned long long *)dst |=
|
|
(unsigned long long)*(b + 3) << 32;
|
|
*(unsigned long long *)dst |=
|
|
(unsigned long long)*(b + 2) << 40;
|
|
*(unsigned long long *)dst |=
|
|
(unsigned long long)*(b + 1) << 48;
|
|
*(unsigned long long *)dst |=
|
|
(unsigned long long)*(b + 0) << 56;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
/*
|
|
* This is only for byte sequences with LSB order, or where
|
|
* where order does not matter
|
|
*/
|
|
memcpy(dst, b, size);
|
|
break;
|
|
}
|
|
|
|
read_context->position += size;
|
|
}
|
|
|
|
/*
|
|
* fetch_generic_descriptor()
|
|
*/
|
|
static void fetch_generic_descriptor(
|
|
generic_descriptor_t *gd,
|
|
read_context_t *rctx)
|
|
{
|
|
fetch_next_field(&gd->flag_required, rctx, 2);
|
|
fetch_next_field(&gd->flag_reserved, rctx, 1);
|
|
fetch_next_field(&gd->data_type_tag, rctx, 1);
|
|
fetch_next_field(&gd->flag, rctx, 1);
|
|
fetch_next_field(&gd->payload, rctx, 13);
|
|
}
|
|
|
|
/*
|
|
* fetch_timing_descriptor()
|
|
*/
|
|
static void fetch_timing_descriptor(
|
|
timing_descriptor_t *td,
|
|
read_context_t *rctx)
|
|
{
|
|
fetch_next_field(&td->pixel_clock, rctx, 2);
|
|
fetch_next_field(&td->h_active, rctx, 1);
|
|
fetch_next_field(&td->h_blanking, rctx, 1);
|
|
fetch_next_field(&td->h_active_blanking_hb, rctx, 1);
|
|
fetch_next_field(&td->v_active, rctx, 1);
|
|
fetch_next_field(&td->v_blanking, rctx, 1);
|
|
fetch_next_field(&td->v_active_blanking_hb, rctx, 1);
|
|
fetch_next_field(&td->h_sync_offset, rctx, 1);
|
|
fetch_next_field(&td->h_sync_pulse_width, rctx, 1);
|
|
fetch_next_field(&td->vs_offset_pulse_width, rctx, 1);
|
|
fetch_next_field(&td->offset_pulse_width_hb, rctx, 1);
|
|
fetch_next_field(&td->h_image_size, rctx, 1);
|
|
fetch_next_field(&td->v_image_size, rctx, 1);
|
|
fetch_next_field(&td->hv_image_size, rctx, 1);
|
|
fetch_next_field(&td->h_border, rctx, 1);
|
|
fetch_next_field(&td->v_border, rctx, 1);
|
|
fetch_next_field(&td->flags, rctx, 1);
|
|
}
|
|
|
|
/*
|
|
* fetch_block_zero()
|
|
* - ebz : structure representing edid block zero to be filled in
|
|
* - data: buffer of 128 bytes containing raw edid data supplied by TV
|
|
*/
|
|
static otm_hdmi_ret_t fetch_block_zero(edid_block_zero_t *ebz,
|
|
unsigned char *data)
|
|
{
|
|
otm_hdmi_ret_t rc = OTM_HDMI_SUCCESS;
|
|
read_context_t read_context = {data, 0};
|
|
read_context_t *rctx = &read_context;
|
|
int i;
|
|
|
|
VERIFY(ebz != NULL, rc, OTM_HDMI_ERR_NULL_ARG, exit);
|
|
VERIFY(data != NULL, rc, OTM_HDMI_ERR_NULL_ARG, exit);
|
|
|
|
/* EDID signature */
|
|
fetch_next_field(&ebz->signature, rctx, 8);
|
|
|
|
/* Manufacturer name id */
|
|
fetch_next_field(&ebz->manufacturer_id, rctx, 2);
|
|
|
|
/* Product code */
|
|
fetch_next_field(&ebz->product_code, rctx, 2);
|
|
|
|
/* Serial number */
|
|
fetch_next_field(&ebz->serial_number, rctx, 4);
|
|
|
|
/* Manufacture week */
|
|
fetch_next_field(&ebz->manufacture_week, rctx, 1);
|
|
|
|
/* Manufacture year */
|
|
fetch_next_field(&ebz->manufacture_year, rctx, 1);
|
|
|
|
/* EDID version */
|
|
fetch_next_field(&ebz->version, rctx, 1);
|
|
|
|
/* EDID revision */
|
|
fetch_next_field(&ebz->revision, rctx, 1);
|
|
|
|
/* Video input definition */
|
|
fetch_next_field(&ebz->video_input_definition, rctx, 1);
|
|
|
|
/* Max horizontal image size */
|
|
fetch_next_field(&ebz->max_horz_image_size, rctx, 1);
|
|
|
|
/* Max vertical image size*/
|
|
fetch_next_field(&ebz->max_vert_image_size, rctx, 1);
|
|
|
|
/* Gamma */
|
|
fetch_next_field(&ebz->gamma, rctx, 1);
|
|
|
|
/* Feature support */
|
|
fetch_next_field(&ebz->feature_support, rctx, 1);
|
|
|
|
/* Color characteristics */
|
|
fetch_next_field(&ebz->rg_lowbits, rctx, 1);
|
|
fetch_next_field(&ebz->bw_lowbits, rctx, 1);
|
|
fetch_next_field(&ebz->red_x, rctx, 1);
|
|
fetch_next_field(&ebz->red_y, rctx, 1);
|
|
fetch_next_field(&ebz->green_x, rctx, 1);
|
|
fetch_next_field(&ebz->green_y, rctx, 1);
|
|
fetch_next_field(&ebz->blue_x, rctx, 1);
|
|
fetch_next_field(&ebz->blue_y, rctx, 1);
|
|
fetch_next_field(&ebz->white_x, rctx, 1);
|
|
fetch_next_field(&ebz->white_x, rctx, 1);
|
|
|
|
/* Established timings */
|
|
fetch_next_field(&ebz->est_timing_1, rctx, 1);
|
|
fetch_next_field(&ebz->est_timing_2, rctx, 1);
|
|
fetch_next_field(&ebz->est_timing_3, rctx, 1);
|
|
|
|
/* Standard timings */
|
|
for (i = 0; i < EDID_STD_TIMINGS_NUM; i++)
|
|
fetch_next_field(&ebz->std_timings[i], rctx, 2);
|
|
|
|
/* Detailed timing descriptors 1 and 2 */
|
|
fetch_generic_descriptor((generic_descriptor_t *) &ebz->td_1, rctx);
|
|
fetch_generic_descriptor((generic_descriptor_t *) &ebz->td_2, rctx);
|
|
|
|
/* Monitor Descriptors 1 and 2 */
|
|
fetch_generic_descriptor((generic_descriptor_t *) &ebz->md_1, rctx);
|
|
fetch_generic_descriptor((generic_descriptor_t *) &ebz->md_2, rctx);
|
|
|
|
/* Number of 128 byte blocks to follow */
|
|
fetch_next_field(&ebz->num_extentions, rctx, 1);
|
|
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* fetch_extension_block_cea()
|
|
*/
|
|
static otm_hdmi_ret_t fetch_extension_block_cea(extention_block_cea_t *eb,
|
|
unsigned char *data)
|
|
{
|
|
otm_hdmi_ret_t rc = OTM_HDMI_SUCCESS;
|
|
read_context_t read_context = {data, 0};
|
|
read_context_t *rctx = &read_context;
|
|
|
|
VERIFY(eb != NULL, rc, OTM_HDMI_ERR_NULL_ARG, exit);
|
|
VERIFY(data != NULL, rc, OTM_HDMI_ERR_NULL_ARG, exit);
|
|
|
|
fetch_next_field(&eb->tag, rctx, 1);
|
|
fetch_next_field(&eb->revision, rctx, 1);
|
|
fetch_next_field(&eb->content_offset, rctx, 1);
|
|
fetch_next_field(&eb->flags, rctx, 1);
|
|
fetch_next_field(&eb->data, rctx, 124);
|
|
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* encode_refresh()
|
|
* Convert an integer refresh rate to the equivalent enumeration
|
|
*/
|
|
static otm_hdmi_refresh_t encode_refresh(unsigned refresh)
|
|
{
|
|
/* Both 1/1001 and 1/1000 refresh rates are included in case
|
|
* EDID contains 1/1001 pixel clock values in the entry
|
|
*/
|
|
switch (refresh) {
|
|
case 23:
|
|
return OTM_HDMI_REFRESH_24;
|
|
case 24:
|
|
return OTM_HDMI_REFRESH_24;
|
|
case 25:
|
|
return OTM_HDMI_REFRESH_25;
|
|
case 29:
|
|
return OTM_HDMI_REFRESH_30;
|
|
case 30:
|
|
return OTM_HDMI_REFRESH_30;
|
|
case 50:
|
|
return OTM_HDMI_REFRESH_50;
|
|
case 59:
|
|
return OTM_HDMI_REFRESH_60;
|
|
case 60:
|
|
return OTM_HDMI_REFRESH_60;
|
|
}
|
|
return OTM_HDMI_REFRESH_USER_DEFINED;
|
|
}
|
|
|
|
/*
|
|
* Returns index of timing with given VIC in given table; -1 otherwise
|
|
*/
|
|
static int find_timing_by_vic_tp(const otm_hdmi_timing_t **set, int size,
|
|
unsigned int vic)
|
|
{
|
|
int i, rc = -1;
|
|
|
|
VERIFY(set, rc, -1, exit);
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (set[i]->metadata == vic) {
|
|
rc = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Timings comparison
|
|
*/
|
|
static bool __timing_equal(const otm_hdmi_timing_t *t1,
|
|
const otm_hdmi_timing_t *t2)
|
|
{
|
|
unsigned int t1_flags = t1->mode_info_flags &
|
|
(PD_SCAN_INTERLACE | PD_AR_16_BY_9);
|
|
unsigned int t2_flags = t2->mode_info_flags &
|
|
(PD_SCAN_INTERLACE | PD_AR_16_BY_9);
|
|
|
|
return ((t1->width == t2->width) &&
|
|
(t1->height == t2->height) &&
|
|
(t1->refresh == t2->refresh) &&
|
|
(t1_flags == t2_flags) && (t1->stereo_type == t2->stereo_type));
|
|
}
|
|
|
|
/*
|
|
* Returns index of given timings in given table of timings; -1 otherwise
|
|
*/
|
|
int find_timing(const otm_hdmi_timing_t *set, int size,
|
|
const otm_hdmi_timing_t *e)
|
|
{
|
|
int i, rc = -1;
|
|
VERIFY(set && e, rc, -1, exit);
|
|
for (i = 0; i < size && !__timing_equal(&set[i], e); i++);
|
|
|
|
rc = (i < size) ? i : -1;
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Returns index of given timings in given table of timing pointers;
|
|
* -1 otherwise
|
|
*/
|
|
static int find_timing_tp(const otm_hdmi_timing_t **set, int size,
|
|
const otm_hdmi_timing_t *e)
|
|
{
|
|
int i, rc = -1;
|
|
VERIFY(set && e, rc, -1, exit);
|
|
for (i = 0; i < size && !__timing_equal(set[i], e); i++);
|
|
|
|
rc = (i < size) ? i : -1;
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* add_timings()
|
|
*/
|
|
static otm_hdmi_ret_t add_timings(edid_info_t *edid,
|
|
const otm_hdmi_timing_t *pdt,
|
|
unsigned int order)
|
|
{
|
|
otm_hdmi_ret_t rc = OTM_HDMI_SUCCESS;
|
|
int i, j;
|
|
|
|
/* Safety checks */
|
|
VERIFY((edid != NULL) && (pdt != NULL), rc,
|
|
OTM_HDMI_ERR_NULL_ARG, exit);
|
|
|
|
/* Is there room for more timings at all? */
|
|
VERIFY(edid->num_timings < MAX_TIMINGS, rc,
|
|
OTM_HDMI_ERR_INVAL, exit);
|
|
|
|
/* Print info about it */
|
|
print_pd_timing(pdt, order);
|
|
|
|
/* Do not add modes that we dont support */
|
|
i = find_timing_tp(edid->ref_timings, edid->num_ref_timings, pdt);
|
|
VERIFY(i > -1, rc, OTM_HDMI_ERR_INVAL, exit);
|
|
|
|
/* Do not add duplicates; Update discovery order though for cases when
|
|
* a mode was decoded from DTD first
|
|
*/
|
|
if ((j = find_timing(edid->timings, edid->num_timings, pdt)) != -1) {
|
|
edid->order[j] = !edid->order[j] ? order : edid->order[j];
|
|
goto exit;
|
|
}
|
|
/* Save discovery order */
|
|
edid->order[edid->num_timings] = order;
|
|
|
|
/* Add timing */
|
|
edid->timings[edid->num_timings++] = *edid->ref_timings[i];
|
|
|
|
/* Update supported family of refresh rates */
|
|
edid->supports_60Hz = edid->supports_60Hz
|
|
|| pdt->refresh == OTM_HDMI_REFRESH_60;
|
|
edid->supports_50Hz = edid->supports_50Hz
|
|
|| pdt->refresh == OTM_HDMI_REFRESH_50;
|
|
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* checksum_valid()
|
|
*/
|
|
static bool checksum_valid(unsigned char *buffer, int size)
|
|
{
|
|
unsigned char sum_computed = 0;
|
|
|
|
while (size-- > 0)
|
|
sum_computed += *(buffer++);
|
|
|
|
return (sum_computed == 0) ? true : false;
|
|
}
|
|
|
|
/*
|
|
* decode_speaker_allocation_data_block()
|
|
*/
|
|
static void decode_speaker_allocation_data_block(unsigned char *e, int n,
|
|
edid_info_t *edid)
|
|
{
|
|
int ne = n / 3;
|
|
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "[speaker block]\n");
|
|
|
|
while (ne-- > 0) {
|
|
edid->speaker_map =
|
|
(unsigned)*e | (unsigned)(*(e + 1) & 0x7) << 8;
|
|
print_speaker_layout(edid->speaker_map);
|
|
e++;
|
|
e++; /* skip the rest of the block */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* decode_video_data_block()
|
|
*/
|
|
static void decode_video_data_block(unsigned char *e, int n, edid_info_t *edid)
|
|
{
|
|
int vic, j, i = 0;
|
|
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "[video data block]\n");
|
|
|
|
while (n-- > 0) {
|
|
vic = *e & 0x7F;
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"- mode #%d %s\n", vic, (*e & 0x80) ? "native" : "");
|
|
|
|
if ((j =
|
|
find_timing_by_vic_tp(edid->ref_timings,
|
|
edid->num_ref_timings, vic)) != -1) {
|
|
add_timings(edid, edid->ref_timings[j], ++i);
|
|
|
|
/* Handle native mode */
|
|
if (*e & 0x80) {
|
|
edid->native_idx =
|
|
find_timing(edid->timings,
|
|
edid->num_timings,
|
|
edid->ref_timings[j]);
|
|
}
|
|
}
|
|
e++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* decode_audio_data_block()
|
|
*/
|
|
static void decode_audio_data_block(unsigned char *e, int n, edid_info_t *edid)
|
|
{
|
|
int ne = n / 3;
|
|
otm_hdmi_audio_cap_t *adb = (otm_hdmi_audio_cap_t *) &edid->audio_caps;
|
|
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "[audio data block... %d entries]\n", ne);
|
|
|
|
edid->short_audio_descriptor_count = ne;
|
|
if (ne > 0)
|
|
memcpy(edid->short_audio_descriptor_data, e, n);
|
|
|
|
while (ne-- > 0) {
|
|
/* Do we have room for another capability? */
|
|
if (edid->num_caps < MAX_CAPS) {
|
|
adb[edid->num_caps].format = (*e & 0x78) >> 3;
|
|
adb[edid->num_caps].max_channels = (*e & 0x07) + 1;
|
|
adb[edid->num_caps].fs = *(e + 1) & 0x7F;
|
|
adb[edid->num_caps].ss_bitrate = *(e + 2);
|
|
print_audio_capability(&adb[edid->num_caps]);
|
|
edid->num_caps++;
|
|
}
|
|
/* Go to the next entry of the block */
|
|
e += 3;
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void declare_mandatory_3d(edid_info_t *edid)
|
|
{
|
|
if (true) {
|
|
add_timings(edid, &MODE_1920x1080p24__FP2, 0);
|
|
add_timings(edid, &MODE_1920x1080p24__FP, 0);
|
|
add_timings(edid, &MODE_1920x1080p24__TBH2, 0);
|
|
}
|
|
|
|
if (edid->supports_60Hz) {
|
|
add_timings(edid, &MODE_1280x720p5994_60__FP2, 0);
|
|
add_timings(edid, &MODE_1280x720p5994_60__FP, 0);
|
|
add_timings(edid, &MODE_1920x1080i5994_60__SBSH2, 0);
|
|
add_timings(edid, &MODE_1280x720p5994_60__TBH2, 0);
|
|
}
|
|
|
|
if (edid->supports_50Hz) {
|
|
add_timings(edid, &MODE_1280x720p50__FP2, 0);
|
|
add_timings(edid, &MODE_1280x720p50__FP, 0);
|
|
add_timings(edid, &MODE_1920x1080i50__SBSH2, 0);
|
|
add_timings(edid, &MODE_1280x720p50__TBH2, 0);
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Addition of 3D timing via 2D mode
|
|
*/
|
|
static void add_3d_mode_via_2d(unsigned int vic, unsigned int struc_3d,
|
|
edid_info_t *edid)
|
|
{
|
|
unsigned int j, k, num_timings = edid->num_timings;
|
|
otm_hdmi_timing_t t;
|
|
|
|
struct {
|
|
unsigned int struc;
|
|
unsigned int type;
|
|
} details_3d[] = {
|
|
{
|
|
0x00, OTM_HDMI_STEREO_FRAME_PACKING_2}, {
|
|
0x00, OTM_HDMI_STEREO_FRAME_PACKING}, {
|
|
0x06, OTM_HDMI_STEREO_TOP_BOTTOM_HALF_2}, {
|
|
0x08, OTM_HDMI_STEREO_SIDE_BY_SIDE_HALF_2},};
|
|
|
|
/* Look for an entry with given order among all decoded 2D timings */
|
|
for (j = 0; j < num_timings; j++) {
|
|
if (edid->order[j] == vic) {
|
|
/*
|
|
* Create all required 3D variations for given
|
|
* 2D mode
|
|
*/
|
|
for (k = 0;
|
|
k < sizeof(details_3d) / sizeof(*details_3d);
|
|
k++) {
|
|
if (details_3d[k].struc == struc_3d) {
|
|
t = edid->timings[j];
|
|
t.stereo_type = details_3d[k].type;
|
|
add_timings(edid, &t, 0);
|
|
}
|
|
}
|
|
|
|
/* Entry of given order was found so start looking for
|
|
* new one
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Processing of 3D modes declared via 3D_Struture_ALL and 3D_MASK
|
|
*/
|
|
static void declare_short_3d(
|
|
unsigned int struc_3d, unsigned int mask,
|
|
edid_info_t *edid)
|
|
{
|
|
unsigned int i, j, modes;
|
|
|
|
/* Go through each 2D_VIC specified via 3D_MASK */
|
|
for (i = 0; i < 16; i++, mask = mask >> 1) {
|
|
/*
|
|
* Go through stereo variations specified via
|
|
* 3D_Structure_ALL
|
|
*/
|
|
for (j = 0, modes = struc_3d; j < 16; j++, modes = modes >> 1) {
|
|
if (modes & 0x01 && mask & 0x01)
|
|
add_3d_mode_via_2d(i + 1, j, edid);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Processing of 3D modes declared via explicit list of 2D vics
|
|
*/
|
|
static void declare_explicit_3d(
|
|
unsigned char *e,
|
|
unsigned int n,
|
|
edid_info_t *edid)
|
|
{
|
|
unsigned int i;
|
|
|
|
/* Go through the list of 2D_VIC_ORDER_X entries
|
|
* 0x08 typed entries are 2 bytes, others are 1 byte
|
|
*/
|
|
for (i = 0; i < n; i += (e[i] & 0x0F) == 0x08 ? 2 : 1)
|
|
add_3d_mode_via_2d(((e[i] & 0xF0) >> 4) + 1, e[i] & 0x0F, edid);
|
|
}
|
|
|
|
/*
|
|
* decode_misc_modes()
|
|
*/
|
|
static void decode_misc_modes(unsigned char *e,
|
|
int n, edid_info_t *edid)
|
|
{
|
|
|
|
}
|
|
|
|
#ifdef OTM_HDMI_3D_ENABLE
|
|
/* TODO: Should be Revisted
|
|
* 3D parsing hangs system with edid09 of analyzer Bug206379 */
|
|
/*
|
|
* decode_3D_modes()
|
|
*/
|
|
void decode_3D_modes(unsigned char *e, int n, int layout, edid_info_t *edid)
|
|
{
|
|
unsigned int offset;
|
|
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "- 3D modes supported:\n");
|
|
|
|
/* Declare mandatory modes */
|
|
declare_mandatory_3d(edid);
|
|
|
|
/* There are several ways of declaring 3D modes support */
|
|
switch (layout) {
|
|
case 0: /* Mandatory modes only */
|
|
offset = 0;
|
|
break;
|
|
|
|
case 1: /* Mandatory modes + variations described in
|
|
* 3D_Structure_ALL */
|
|
/* supported by each of first 16 VICs */
|
|
offset = 2;
|
|
declare_short_3d(e[1] | (e[0] << 8), 0xFFFF, edid);
|
|
break;
|
|
|
|
case 2: /* Mandatory modes + variations described in
|
|
* 3D_Structure_ALL */
|
|
/* supported only by some of 16 first VICs
|
|
* [as told by 3D_MASK] */
|
|
offset = 4;
|
|
declare_short_3d(e[1] | (e[0] << 8), e[3] | (e[2] << 8), edid);
|
|
break;
|
|
|
|
default: /* Reserved for future use */
|
|
offset = 0;
|
|
break;
|
|
}
|
|
|
|
/* Declare 3D modes based on present 2D VIC entries */
|
|
declare_explicit_3d(e + offset,
|
|
(n >= offset) ? n - offset : 0, edid);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* decode_vendor_data_block()
|
|
*/
|
|
static void decode_vendor_data_block(unsigned char *e,
|
|
int n, edid_info_t *edid)
|
|
{
|
|
unsigned int pos;
|
|
#ifdef OTM_HDMI_3D_ENABLE
|
|
unsigned int len_3d, len_hdmi;
|
|
#endif
|
|
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"[vendor specific data block.. length %d]\n", n);
|
|
|
|
/* Look for HDMI signature [0x030C00] */
|
|
if (n >= 3) {
|
|
if ((e[0] == 0x03) && (e[1] == 0x0C) && (e[2] == 0x00)) {
|
|
edid->hdmi = true;
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "- HDMI signature found\n");
|
|
}
|
|
}
|
|
/* Parse Source Physical Address */
|
|
if (n >= 5)
|
|
edid->spa = (e[3] << 8) | e[4];
|
|
/* Look for more optional stuff */
|
|
if (n >= 6) {
|
|
/* Deep Color support */
|
|
edid->dc_48 = (e[5] & 0x40) != 0;
|
|
edid->dc_36 = (e[5] & 0x20) != 0;
|
|
edid->dc_30 = (e[5] & 0x10) != 0;
|
|
edid->dc_y444 = (e[5] & 0x08) != 0;
|
|
|
|
/* AI support */
|
|
edid->supports_ai = (e[5] & 0x80) != 0;
|
|
}
|
|
/* MAX TMDS clock */
|
|
if (n >= 7)
|
|
edid->max_tmds_clock = e[6] * 5;
|
|
/* Check for optional latency and 3D fields */
|
|
if ((n >= 8)) {
|
|
edid->latency_present = (e[7] & 0x80) != 0;
|
|
edid->latency_int_present = (e[7] & 0x40) != 0;
|
|
edid->hdmi_video_present = (e[7] & 0x20) != 0;
|
|
}
|
|
/* From now on keep explicit track of position we are reading */
|
|
pos = 8;
|
|
|
|
/* Get video latency [in ms] */
|
|
if (edid->latency_present) {
|
|
edid->latency_video = e[pos++];
|
|
edid->latency_audio = e[pos++];
|
|
|
|
/* Get interlaced video latency [in ms] */
|
|
if (edid->latency_int_present) {
|
|
edid->latency_video_interlaced = e[pos++];
|
|
edid->latency_audio_interlaced = e[pos++];
|
|
}
|
|
}
|
|
|
|
#ifdef OTM_HDMI_3D_ENABLE
|
|
/* 3D and misc modes information from HDMI 1.4 specification */
|
|
if (edid->hdmi_video_present) {
|
|
edid->enabled_3d = (e[pos++] & 0x80) != 0;
|
|
|
|
len_3d = (e[pos] & 0x1F);
|
|
len_hdmi = (e[pos++] & 0xE0) >> 5;
|
|
|
|
/* Assumption is that both misc and 3D modes can be
|
|
* present so deal with
|
|
* misc modes first
|
|
*/
|
|
decode_misc_modes(e + pos, len_hdmi, edid);
|
|
|
|
/* Now deal with 3D stuff */
|
|
if (len_3d || edid->enabled_3d) {
|
|
decode_3D_modes(e + pos + len_hdmi, len_3d,
|
|
(e[pos - 2] & 0x60) >> 5, edid);
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
/*
|
|
* decode_extended_data_block()
|
|
*/
|
|
static void decode_extended_data_block(unsigned char *e,
|
|
int n, edid_info_t *edid)
|
|
{
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "[extended data block.. length %d]\n", n);
|
|
|
|
switch (*(e + 0)) {
|
|
case 0x00: /* Video Capability Block */
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "Video Capability Block\n");
|
|
edid->rgb_quant_selectable = *(e + 1) & 0x40;
|
|
edid->ycc_quant_selectable = *(e + 1) & 0x80;
|
|
break;
|
|
case 0x01: /* Vendor Specific Video Data Block */
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"Vendor Specific Video Data Block\n");
|
|
break;
|
|
case 0x05: /* Colorimetry Block */
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "Colorimetry Block\n");
|
|
if (n == 3) {
|
|
edid->xvycc601 = (*(e + 1) & 0x01) != 0;
|
|
edid->xvycc709 = (*(e + 1) & 0x02) != 0;
|
|
}
|
|
break;
|
|
case 0x11: /* CEA Misc Audio Block */
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "CEA Misc Audio Block\n");
|
|
break;
|
|
case 0x12: /* Vendor specific audio data block */
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"Vendor specific audio data Block\n");
|
|
break;
|
|
default: /* reserved blocks */
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "Reserved Block\n");
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* This is what short descriptor handler signature should look like
|
|
* NOTE: e is where payload starts, i.e. header byte is not included!!!
|
|
*/
|
|
typedef void (*short_block_decoder_t)(unsigned char *e, int n,
|
|
edid_info_t *edid);
|
|
|
|
static short_block_decoder_t short_block_decoders[] = {
|
|
/* Reserved */
|
|
NULL,
|
|
/* Audio data block decoder */
|
|
decode_audio_data_block,
|
|
/* Video data block decoder */
|
|
decode_video_data_block,
|
|
/* Vendor specific block decoder */
|
|
decode_vendor_data_block,
|
|
/* Speaker allocation block decoder */
|
|
decode_speaker_allocation_data_block,
|
|
/* VESA DTC data block decoder */
|
|
NULL,
|
|
/* Reserved */
|
|
NULL,
|
|
/* Extended tag handler */
|
|
decode_extended_data_block
|
|
};
|
|
|
|
/*
|
|
* decode_block_collection()
|
|
* See section 7.5 of CEA-861-C for details
|
|
*/
|
|
static void decode_block_collection(extention_block_cea_t *eb,
|
|
edid_info_t *edid)
|
|
{
|
|
unsigned char *c = eb->data;
|
|
int block_type, payload_size;
|
|
|
|
/* All area before detailed descriptors should be filled
|
|
*/
|
|
while (c < ((unsigned char *)eb + eb->content_offset)) {
|
|
block_type = (*c & 0xE0) >> 5;
|
|
payload_size = *c & 0x1F;
|
|
|
|
/* Simple block types */
|
|
if ((block_type < 8) && (block_type >= 0)) {
|
|
if (short_block_decoders[block_type]) {
|
|
short_block_decoders[block_type] ((unsigned char
|
|
*)c + 1,
|
|
payload_size,
|
|
edid);
|
|
} else {
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"[block 0x%x.. TBA]\n", block_type);
|
|
}
|
|
}
|
|
/* Unknown */
|
|
else
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"[unknown block 0x%x]\n", (int)*c);
|
|
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "\n");
|
|
c += (*c & 0x1F) + 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* decode_standard_timings()
|
|
* Section 3.9.1 of EDID STD
|
|
*/
|
|
static void decode_standard_timings(unsigned short st, edid_info_t *edid)
|
|
{
|
|
struct {
|
|
int h;
|
|
int v;
|
|
} ar[] = { {
|
|
16, 10}, {
|
|
4, 3}, {
|
|
5, 4}, {
|
|
16, 9} };
|
|
otm_hdmi_timing_t pdt;
|
|
int r;
|
|
|
|
if (st != 0x0101) {
|
|
pdt.width = ((st & 0x00FF) + 31) * 8;
|
|
pdt.refresh = encode_refresh(((st & 0x3F00) >> 8) + 60);
|
|
|
|
r = ((st & 0xC000) >> 14);
|
|
|
|
/* Init flags with respect to aspect ratio */
|
|
pdt.mode_info_flags = (r == 3) ? PD_AR_16_BY_9 : 0;
|
|
|
|
/* TODO: Add proper logic for EDID earlier than 1.3
|
|
* This weird line below makes sure division by zero is avoided
|
|
*/
|
|
pdt.height =
|
|
pdt.width * ar[r].v / (ar[r].h ? ar[r].h : ar[r].v);
|
|
|
|
/* Indicate no stereo support */
|
|
pdt.stereo_type = OTM_HDMI_STEREO_NONE;
|
|
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "[Standart timing]\n");
|
|
add_timings(edid, &pdt, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* decode_detailed_timings()
|
|
* Table 3.16 of EDID STD
|
|
*/
|
|
static bool decode_detailed_timings(timing_descriptor_t *td,
|
|
otm_hdmi_timing_t *pdt)
|
|
{
|
|
bool rc = true;
|
|
|
|
int pixel_clock = td->pixel_clock * 10;
|
|
int h_active = ((td->h_active_blanking_hb & 0xF0) << 4) | td->h_active;
|
|
int h_blanking =
|
|
((td->h_active_blanking_hb & 0x0F) << 8) | td->h_blanking;
|
|
int v_active = ((td->v_active_blanking_hb & 0xF0) << 4) | td->v_active;
|
|
int v_blanking =
|
|
((td->v_active_blanking_hb & 0x0F) << 8) | td->v_blanking;
|
|
int h_sync_off =
|
|
((td->offset_pulse_width_hb & 0xC0) << 2) | td->h_sync_offset;
|
|
int h_sync_pw =
|
|
((td->offset_pulse_width_hb & 0x30) << 4) | td->h_sync_pulse_width;
|
|
|
|
int v_sync_off = ((td->vs_offset_pulse_width & 0xF0) >> 4)
|
|
| (td->offset_pulse_width_hb & 0x0C);
|
|
|
|
int v_sync_pw = ((td->offset_pulse_width_hb & 0x03) << 4)
|
|
| (td->vs_offset_pulse_width & 0x0F);
|
|
|
|
int h_img_size = ((td->hv_image_size & 0xF0) << 4) | td->h_image_size;
|
|
int v_img_size = ((td->hv_image_size & 0x0F) << 8) | td->v_image_size;
|
|
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "[detailed timing descriptor]\n");
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
" - pixel_clock : %d KHz\n", pixel_clock);
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
" - horz_active : %d pixels\n", h_active);
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
" - horz_blanking : %d pixels\n", h_blanking);
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
" - vert_active : %d lines\n", v_active);
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
" - vert_blanking : %d lines\n", v_blanking);
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
" - horz_sync_off : %d pixels\n", h_sync_off);
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
" - horz_sync_pw : %d pixels\n", h_sync_pw);
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
" - vert_sync_off : %d lines\n", v_sync_off);
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
" - vert_sync_pw : %d lines\n", v_sync_pw);
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
" - image ratio : %d : %d\n", h_img_size, v_img_size);
|
|
|
|
pdt->width = h_active;
|
|
pdt->htotal = h_active + h_blanking;
|
|
pdt->hblank_start = h_active;
|
|
pdt->hblank_end = h_active + h_blanking;
|
|
pdt->hsync_start = h_active + h_sync_off;
|
|
pdt->hsync_end = h_active + h_sync_off + h_sync_pw;
|
|
|
|
pdt->height = v_active;
|
|
pdt->vtotal = v_active + v_blanking;
|
|
pdt->vblank_start = v_active;
|
|
pdt->vblank_end = v_active + v_blanking;
|
|
pdt->vsync_start = v_active + v_sync_off;
|
|
pdt->vsync_end = v_active + v_sync_off + v_sync_pw;
|
|
|
|
pdt->dclk = pixel_clock;
|
|
|
|
/* Make sure we are seeing valid mode */
|
|
VERIFY(pdt->htotal && pdt->vtotal && pdt->dclk, rc, false, exit);
|
|
|
|
pdt->refresh = (pdt->dclk * 1000) / (pdt->htotal * pdt->vtotal);
|
|
|
|
/* Convert refresh velue to enumeration entry */
|
|
pdt->refresh = encode_refresh(pdt->refresh);
|
|
|
|
/* Check if mode is interlaced */
|
|
pdt->mode_info_flags |= (td->flags & 0x80) ? PD_SCAN_INTERLACE : 0;
|
|
|
|
/* Determine picture aspect ratio */
|
|
pdt->mode_info_flags |=
|
|
(h_img_size / 4 == v_img_size / 3) ? 0 : PD_AR_16_BY_9;
|
|
|
|
/* Check for sync signal polarity */
|
|
if (td->flags & 0x18) {
|
|
pdt->mode_info_flags |= (td->flags & 0x02) ? PD_HSYNC_HIGH : 0;
|
|
pdt->mode_info_flags |= (td->flags & 0x04) ? PD_VSYNC_HIGH : 0;
|
|
}
|
|
/* Indicate no stereo support */
|
|
pdt->stereo_type = OTM_HDMI_STEREO_NONE;
|
|
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* decode_generic_descriptor()
|
|
* Table 3.19. Table 3.20 of EDID STD
|
|
*/
|
|
static void decode_generic_descriptor(generic_descriptor_t *gd,
|
|
edid_info_t *edid)
|
|
{
|
|
int i;
|
|
|
|
/* Not a timing descriptor */
|
|
if ((gd->flag_required == 0) && (gd->flag_reserved == 0)
|
|
&& (gd->flag == 0)) {
|
|
switch (gd->data_type_tag) {
|
|
case 0xFF:
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"[Monitor Serial Number ]\n");
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, " - %s\n", gd->payload);
|
|
break;
|
|
case 0xFE:
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"[ASCII String ]\n");
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, " - %s\n", gd->payload);
|
|
break;
|
|
case 0xFD:
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"[Monitor Range Limits ]\n");
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, " - ...\n");
|
|
break;
|
|
case 0xFC:
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"[Monitor Name ]\n");
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, " - %s\n", gd->payload);
|
|
for (i = 0; i < 13; i++) {
|
|
if (gd->payload[i] == '\n')
|
|
break;
|
|
else
|
|
edid->product_name[i] = gd->payload[i];
|
|
}
|
|
break;
|
|
case 0xFB:
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"[Color Data ]\n");
|
|
break;
|
|
case 0xFA:
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"[More Standard Timings ]\n");
|
|
for (i = 0; i < 12; i += 2) {
|
|
decode_standard_timings
|
|
((gd->payload[i] << 8) | gd->payload[i + 1],
|
|
edid);
|
|
}
|
|
break;
|
|
case 0x10:
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"[Dummy ]\n");
|
|
break;
|
|
default:
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"[Manufacturer/Undefined]\n");
|
|
break;
|
|
}
|
|
}
|
|
/* Timing descriptor */
|
|
else {
|
|
otm_hdmi_timing_t pdt;
|
|
memset(&pdt, 0, sizeof(otm_hdmi_timing_t));
|
|
if (decode_detailed_timings((timing_descriptor_t *) gd, &pdt))
|
|
add_timings(edid, &pdt, 0);
|
|
}
|
|
}
|
|
|
|
#define __BASIC_AUDIO_FS (OTM_HDMI_AUDIO_FS_32_KHZ | \
|
|
OTM_HDMI_AUDIO_FS_44_1_KHZ | \
|
|
OTM_HDMI_AUDIO_FS_48_KHZ)
|
|
|
|
/*
|
|
* __find_and_declare_basic_audio_support()
|
|
*/
|
|
static otm_hdmi_ret_t __find_and_declare_basic_audio_support(edid_info_t *edid)
|
|
{
|
|
otm_hdmi_audio_cap_t *caps = edid->audio_caps;
|
|
unsigned int i = 0;
|
|
|
|
otm_hdmi_audio_cap_t cap = {
|
|
.format = OTM_HDMI_AUDIO_FORMAT_PCM,
|
|
.max_channels = 2,
|
|
.fs = __BASIC_AUDIO_FS,
|
|
.ss_bitrate = OTM_HDMI_AUDIO_SS_16,
|
|
};
|
|
|
|
/* Try to find basic audio functionality among declared capabilities */
|
|
for (i = 0; i < edid->num_caps; i++) {
|
|
if ((caps[i].format == cap.format)
|
|
&& (caps[i].max_channels >= cap.max_channels)
|
|
&& (caps[i].ss_bitrate & cap.ss_bitrate)
|
|
&& ((caps[i].fs & cap.fs) == cap.fs)) {
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
/* Add RLRF pair to the speaker allocation map */
|
|
edid->speaker_map |= 0x01;
|
|
|
|
/* Add basic audio capability to the list of capabilities */
|
|
memcpy(&caps[edid->num_caps++], &cap, sizeof(cap));
|
|
|
|
exit:
|
|
return OTM_HDMI_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* decode_extention_block_cea()
|
|
* Refer to section A.2.13 of CEA-861-C document for additional details
|
|
*/
|
|
static void decode_extention_block_cea(extention_block_cea_t *eb,
|
|
edid_info_t *edid)
|
|
{
|
|
int i;
|
|
generic_descriptor_t gd;
|
|
|
|
/* Check YCbCr444 and YCbCr422 support */
|
|
if (eb->revision >= 2) {
|
|
edid->ycbcr444 = (eb->flags & 0x20) ? true : false;
|
|
edid->ycbcr422 = (eb->flags & 0x10) ? true : false;
|
|
}
|
|
/*
|
|
* Short descriptors section exists when:
|
|
* - offset is not 4
|
|
* - CEA extension version is 3
|
|
*/
|
|
if ((eb->content_offset != 4) && (eb->revision >= 3))
|
|
decode_block_collection(eb, edid);
|
|
/*
|
|
* Detailed timing descriptors:
|
|
* - do not exist when offset is zero
|
|
* - may still not exist even when offset is non-zero; in this case
|
|
* location where they would exist is padded so make sure
|
|
* appropriate decoding routine can handle that padding correctly
|
|
*/
|
|
#define __DSCR_SIZE 18
|
|
if (eb->content_offset != 0) {
|
|
for (i = 0; i < (128 - eb->content_offset) / __DSCR_SIZE; i++) {
|
|
/*
|
|
* Instead of using a casted pointer to descriptor,
|
|
* we explicitely copy memory content temporary
|
|
* placeholder. This way we are avoiding possible
|
|
* integer addressing problems
|
|
*/
|
|
memcpy(&gd,
|
|
(char *)eb + eb->content_offset +
|
|
__DSCR_SIZE * i, __DSCR_SIZE);
|
|
decode_generic_descriptor(&gd, edid);
|
|
}
|
|
}
|
|
#undef __DSCR_SIZE
|
|
|
|
/* Check for basic audio support and add it to caps list if necessary */
|
|
if ((eb->revision >= 2) && (eb->flags & 0x40))
|
|
__find_and_declare_basic_audio_support(edid);
|
|
}
|
|
|
|
/*
|
|
* fetch_block_map()
|
|
*/
|
|
static otm_hdmi_ret_t fetch_block_map(edid_block_map_t *ebm,
|
|
unsigned char *data)
|
|
{
|
|
otm_hdmi_ret_t rc = OTM_HDMI_SUCCESS;
|
|
read_context_t read_context = { data, 0 };
|
|
read_context_t *rctx = &read_context;
|
|
|
|
VERIFY(ebm != NULL, rc, OTM_HDMI_ERR_INTERNAL, exit);
|
|
VERIFY(data != NULL, rc, OTM_HDMI_ERR_INTERNAL, exit);
|
|
|
|
/* Fill tag, map and checksum fields */
|
|
fetch_next_field(&ebm->tag, rctx, 1);
|
|
fetch_next_field(ebm->map, rctx, BLOCK_MAP_SIZE);
|
|
fetch_next_field(&ebm->checksum, rctx, 1);
|
|
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* block_decode()
|
|
*/
|
|
static otm_hdmi_ret_t block_decode(edid_info_t *edid_info, unsigned int type,
|
|
unsigned char *buffer)
|
|
{
|
|
otm_hdmi_ret_t rc = OTM_HDMI_SUCCESS;
|
|
extention_block_cea_t eb;
|
|
|
|
LOG_PRINT(LOG_LEVEL_DETAIL, "Decoding extension 0x%x\n", type);
|
|
|
|
switch (type) {
|
|
case 0x02:
|
|
VERIFY(buffer[0] == 0x02, rc, OTM_HDMI_ERR_FAILED, exit);
|
|
memset(&eb, 0, sizeof(extention_block_cea_t));
|
|
fetch_extension_block_cea(&eb, buffer);
|
|
decode_extention_block_cea(&eb, edid_info);
|
|
break;
|
|
|
|
default:
|
|
LOG_PRINT(LOG_LEVEL_DETAIL,
|
|
"Extension 0x%x is not supported; Bypassing\n",
|
|
type);
|
|
break;
|
|
}
|
|
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
int edid_parse_pd_timing_from_cea_block(edid_info_t *edid_info,
|
|
unsigned char *buffer,
|
|
otm_hdmi_timing_t *pdts)
|
|
{
|
|
extention_block_cea_t eb;
|
|
int i = 0;
|
|
|
|
if (buffer[0x0] == 0x2) {
|
|
memset(&eb, 0, sizeof(extention_block_cea_t));
|
|
edid_info->num_timings = 0;
|
|
fetch_extension_block_cea(&eb, buffer);
|
|
decode_extention_block_cea(&eb, edid_info);
|
|
|
|
for (i = 0; i < edid_info->num_timings; i++) {
|
|
memcpy((unsigned char *)&pdts[i],
|
|
(unsigned char *)&edid_info->timings[i],
|
|
sizeof(otm_hdmi_timing_t));
|
|
}
|
|
return edid_info->num_timings;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* edid_parse()
|
|
*/
|
|
otm_hdmi_ret_t edid_parse(edid_info_t *edid_info,
|
|
i2c_read_t data_read, void *cd)
|
|
{
|
|
unsigned char buffer[SEGMENT_SIZE];
|
|
edid_block_zero_t ebz;
|
|
edid_block_map_t ebm;
|
|
extention_block_cea_t eb;
|
|
otm_hdmi_ret_t rc = OTM_HDMI_SUCCESS;
|
|
int i, sp, offset;
|
|
|
|
/* Read block zero */
|
|
rc = data_read(cd, 0, 0, buffer, SEGMENT_SIZE);
|
|
VERIFY_QUICK(rc == OTM_HDMI_SUCCESS, exit);
|
|
print_raw_block(buffer, SEGMENT_SIZE);
|
|
VERIFY(checksum_valid(buffer, SEGMENT_SIZE), rc,
|
|
OTM_HDMI_ERR_FAILED, exit);
|
|
|
|
/* Process block zero */
|
|
memset(&ebz, 0, sizeof(edid_block_zero_t));
|
|
fetch_block_zero(&ebz, buffer);
|
|
|
|
VERIFY(ebz.signature == EDID_SIGNATURE, rc, OTM_HDMI_ERR_FAILED, exit);
|
|
|
|
/* Decode general stuff */
|
|
edid_info->manufacturer_id = ebz.manufacturer_id;
|
|
edid_info->product_code = ebz.product_code;
|
|
edid_info->product_sn = ebz.serial_number;
|
|
edid_info->product_year = ebz.manufacture_year + 1990;
|
|
edid_info->product_week = ebz.manufacture_week;
|
|
edid_info->max_horz_image_size = ebz.max_horz_image_size;
|
|
edid_info->max_vert_image_size = ebz.max_vert_image_size;
|
|
edid_info->native_idx = -1;
|
|
|
|
/* Decode timings */
|
|
decode_generic_descriptor((generic_descriptor_t *) &ebz.td_1,
|
|
edid_info);
|
|
decode_generic_descriptor((generic_descriptor_t *) &ebz.td_2,
|
|
edid_info);
|
|
decode_generic_descriptor((generic_descriptor_t *) &ebz.md_1,
|
|
edid_info);
|
|
decode_generic_descriptor((generic_descriptor_t *) &ebz.md_2,
|
|
edid_info);
|
|
|
|
for (i = 0; i < EDID_STD_TIMINGS_NUM; i++)
|
|
decode_standard_timings(ebz.std_timings[i], edid_info);
|
|
|
|
/* If there are no extentions - we are done */
|
|
VERIFY(ebz.num_extentions != 0, rc, OTM_HDMI_SUCCESS, exit);
|
|
|
|
/* Read next block */
|
|
rc = data_read(cd, 0, SEGMENT_SIZE, buffer, SEGMENT_SIZE);
|
|
VERIFY_QUICK(rc == OTM_HDMI_SUCCESS, exit);
|
|
print_raw_block(buffer, SEGMENT_SIZE);
|
|
VERIFY(checksum_valid(buffer, SEGMENT_SIZE), rc,
|
|
OTM_HDMI_ERR_FAILED, exit);
|
|
|
|
/* There is only 1 extention; Assume its CEA Extension [tag = 0x02] */
|
|
if (ebz.num_extentions == 1) {
|
|
/* Process extention block */
|
|
memset(&eb, 0, sizeof(extention_block_cea_t));
|
|
fetch_extension_block_cea(&eb, buffer);
|
|
VERIFY(eb.tag == 0x02, rc, OTM_HDMI_SUCCESS, exit);
|
|
decode_extention_block_cea(&eb, edid_info);
|
|
}
|
|
/* There is a block map and more extentions */
|
|
else {
|
|
/* Process block map */
|
|
memset(&ebm, 0, sizeof(edid_block_map_t));
|
|
fetch_block_map(&ebm, buffer);
|
|
|
|
/* Verify we are indeed dealing with map block */
|
|
VERIFY(ebm.tag == 0xF0, rc, OTM_HDMI_ERR_FAILED, exit);
|
|
|
|
/* Deal with each block in the map */
|
|
for (i = 0; (i < BLOCK_MAP_SIZE) && ebm.map[i]; i++) {
|
|
/* Compute extension block location */
|
|
sp = (i + 2) / 2;
|
|
offset = (i % 2) ? SEGMENT_SIZE : 0;
|
|
|
|
/* Read extension block */
|
|
rc = data_read(cd, sp, offset, buffer, SEGMENT_SIZE);
|
|
VERIFY_QUICK(rc == OTM_HDMI_SUCCESS, exit);
|
|
print_raw_block(buffer, SEGMENT_SIZE);
|
|
VERIFY(checksum_valid(buffer, SEGMENT_SIZE), rc,
|
|
OTM_HDMI_ERR_FAILED, exit);
|
|
|
|
/* Decode extension block */
|
|
rc = block_decode(edid_info, ebm.map[i], buffer);
|
|
VERIFY_QUICK(rc == OTM_HDMI_SUCCESS, exit);
|
|
}
|
|
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Read enhanced EDID data blocks at 0xA0 using segment pointer address 0x60
|
|
*/
|
|
static otm_hdmi_ret_t __enhanced_ddc_read_one_block(struct i2c_adapter *adapter,
|
|
unsigned char sp, unsigned char offset, unsigned char *buffer)
|
|
{
|
|
#define SEGMENT_ADDR 0x30
|
|
#define EDID_ADDR 0x50
|
|
struct i2c_msg msgs[] = {
|
|
{
|
|
.addr = SEGMENT_ADDR,
|
|
.flags = 0,
|
|
.len = 1,
|
|
.buf = &sp,
|
|
|
|
}, {
|
|
.addr = EDID_ADDR,
|
|
.flags = 0,
|
|
.len = 1,
|
|
.buf = &offset,
|
|
|
|
}, {
|
|
.addr = EDID_ADDR,
|
|
.flags = I2C_M_RD,
|
|
.len = SEGMENT_SIZE,
|
|
.buf = buffer,
|
|
}
|
|
};
|
|
|
|
int ret_i2c, retries = 5, siz = sizeof(msgs) / sizeof(msgs[0]);
|
|
|
|
pr_debug("enter %s\n", __func__);
|
|
|
|
/* Safty check */
|
|
if (!adapter || !buffer)
|
|
return OTM_HDMI_ERR_FAILED;
|
|
|
|
do {
|
|
ret_i2c = i2c_transfer(adapter, msgs, siz);
|
|
pr_debug("ret_i2c: %d\n", ret_i2c);
|
|
} while (ret_i2c != siz && --retries);
|
|
|
|
if (ret_i2c != siz) {
|
|
pr_err("i2c failed1\n");
|
|
return OTM_HDMI_ERR_FAILED;
|
|
}
|
|
|
|
pr_debug("exit %s\n", __func__);
|
|
|
|
return OTM_HDMI_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* edid_extension_parse()
|
|
*/
|
|
otm_hdmi_ret_t edid_extension_parse(struct i2c_adapter *adapter,
|
|
edid_info_t *edid_info, unsigned char *edid)
|
|
{
|
|
unsigned char *buffer;
|
|
edid_block_map_t ebm;
|
|
extention_block_cea_t eb;
|
|
otm_hdmi_ret_t rc = OTM_HDMI_SUCCESS;
|
|
int i, sp, offset;
|
|
|
|
if (!edid_info || !edid)
|
|
return OTM_HDMI_ERR_FAILED;
|
|
|
|
pr_debug("enter %s\n", __func__);
|
|
|
|
/* If there are no extentions - we are done */
|
|
VERIFY(edid[0x7e] != 0, rc, OTM_HDMI_SUCCESS, exit);
|
|
|
|
/* Point to the first extension block */
|
|
buffer = edid + SEGMENT_SIZE;
|
|
VERIFY(checksum_valid(buffer, SEGMENT_SIZE), rc,
|
|
OTM_HDMI_ERR_FAILED, exit);
|
|
|
|
/* There is only 1 extention; Assume its CEA Extension [tag = 0x02] */
|
|
if (edid[0x7e] == 1) {
|
|
/* Process first extention block */
|
|
memset(&eb, 0, sizeof(extention_block_cea_t));
|
|
fetch_extension_block_cea(&eb, buffer);
|
|
VERIFY(eb.tag == 0x02, rc, OTM_HDMI_SUCCESS, exit);
|
|
decode_extention_block_cea(&eb, edid_info);
|
|
}
|
|
/* There is a block map and more extentions */
|
|
else {
|
|
/* Process block map */
|
|
memset(&ebm, 0, sizeof(edid_block_map_t));
|
|
fetch_block_map(&ebm, buffer);
|
|
|
|
/* Verify we are indeed dealing with map block */
|
|
VERIFY(ebm.tag == 0xF0, rc, OTM_HDMI_ERR_FAILED, exit);
|
|
|
|
/* Deal with each block in the map */
|
|
for (i = 0; (i < BLOCK_MAP_SIZE) && ebm.map[i]; i++) {
|
|
/* Compute extension block location */
|
|
sp = (i + 2) / 2;
|
|
offset = (i % 2) ? SEGMENT_SIZE : 0;
|
|
|
|
/* Read extension block */
|
|
rc = __enhanced_ddc_read_one_block(adapter, sp, offset,
|
|
buffer);
|
|
VERIFY_QUICK(rc == OTM_HDMI_SUCCESS, exit);
|
|
VERIFY(checksum_valid(buffer, SEGMENT_SIZE), rc,
|
|
OTM_HDMI_ERR_FAILED, exit);
|
|
|
|
/* Decode extension block */
|
|
rc = block_decode(edid_info, ebm.map[i], buffer);
|
|
VERIFY_QUICK(rc == OTM_HDMI_SUCCESS, exit);
|
|
}
|
|
}
|
|
exit:
|
|
pr_debug("exit %s (ret = %d)\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|