From 4e355880b6cc9d722d143fac1c959165234d5a2c Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Wed, 19 Jan 2011 17:47:49 -0800 Subject: [PATCH] siihdmi: fix up cea861 header parsing The data block collection can be arbitrarily ordered. We need to iterate over all the blocks and check them. --- drivers/video/mxc/mxcfb_siihdmi.c | 94 ++++++++++++++++++------ include/linux/cea861.h | 118 ++++++++++++++++++++---------- include/linux/edid.h | 16 ++-- 3 files changed, 163 insertions(+), 65 deletions(-) diff --git a/drivers/video/mxc/mxcfb_siihdmi.c b/drivers/video/mxc/mxcfb_siihdmi.c index b5eb8836f77..b1856e842b5 100644 --- a/drivers/video/mxc/mxcfb_siihdmi.c +++ b/drivers/video/mxc/mxcfb_siihdmi.c @@ -197,9 +197,9 @@ static int siihdmi_read_edid(struct siihdmi_tx *tx, u8 *edid, size_t size) /* step 1: (potentially) disable HDCP */ - /* step 2: request the DDC bus */ ctrl = i2c_smbus_read_byte_data(tx->client, SIIHDMI_TPI_REG_SYS_CTRL); + /* step 2: request the DDC bus */ ret = i2c_smbus_write_byte_data(tx->client, SIIHDMI_TPI_REG_SYS_CTRL, ctrl | SIIHDMI_SYS_CTRL_DDC_BUS_REQUEST); @@ -256,12 +256,57 @@ static int siihdmi_read_edid(struct siihdmi_tx *tx, u8 *edid, size_t size) return 0; } +static void siihdmi_parse_cea861_timing_block(struct siihdmi_tx *tx, + const struct edid_extension *ext) +{ + const struct cea861_timing_block * const cea = + (struct cea861_timing_block *) ext; + const u8 offset = offsetof(struct cea861_timing_block, data); + u8 index = 0; + + BUILD_BUG_ON(sizeof(*cea) != sizeof(*ext)); + + tx->enable_audio = cea->basic_audio_supported; + + if (cea->underscan_supported) + tx->pixel_mapping = PIXEL_MAPPING_UNDERSCANNED; + + if (cea->dtd_offset == CEA81_NO_DTDS_PRESENT) + return; + + do { + const struct cea861_data_block_header * const header = + (struct cea861_data_block_header *) &cea->data[index]; + + switch (header->tag) { + case CEA861_DATA_BLOCK_TYPE_VENDOR_SPECIFIC: { + const struct cea861_vendor_specific_data_block * const vsdb = + (struct cea861_vendor_specific_data_block *) header; + + if (!memcmp(vsdb->ieee_registration, + CEA861_OUI_REGISTRATION_ID_HDMI_LSB, + sizeof(vsdb->ieee_registration))) + tx->connection_type = CONNECTION_TYPE_HDMI; + } + break; + default: + break; + } + + index = index + header->length + sizeof(header); + } while (index < cea->dtd_offset - offset); +} + static void siihdmi_detect_sink(struct siihdmi_tx *tx) { - u8 edid[EDID_BLOCK_SIZE << 1]; - const struct edid_block0 * const block0 = (struct edid_block0 *) edid; - const struct edid_extension * const block1 = - (struct edid_extension *) edid + EDID_BLOCK_SIZE; + u8 *edid; + struct edid_extension *extensions, *extension; + struct edid_block0 block0; + u32 length; + u8 i; + + BUILD_BUG_ON(sizeof(block0) != EDID_BLOCK_SIZE); + BUILD_BUG_ON(sizeof(*extension) != EDID_BLOCK_SIZE); /* defaults */ tx->connection_type = CONNECTION_TYPE_DVI; @@ -269,31 +314,38 @@ static void siihdmi_detect_sink(struct siihdmi_tx *tx) tx->enable_audio = false; /* use EDID to identify sink characteristics */ - if (siihdmi_read_edid(tx, edid, sizeof(edid)) < 0) + if (siihdmi_read_edid(tx, (u8 *) &block0, sizeof(block0)) < 0) return; - if (!block0->extensions) + if (!block0.extensions) return; - switch (block1->tag) { - case EDID_EXTENSION_CEA: { - const struct cea_timing_block *ctb = - (struct cea_timing_block *) block1; + /* need to allocate space for block 0 as well as the extensions */ + length = (block0.extensions + 1) * EDID_BLOCK_SIZE; - tx->enable_audio = ctb->basic_audio_supported; + edid = kzalloc(length, GFP_KERNEL); + if (!edid) + return; - if (ctb->underscan_supported) - tx->pixel_mapping = PIXEL_MAPPING_UNDERSCANNED; + if (siihdmi_read_edid(tx, edid, length) < 0) + goto out; - if (!memcmp(ctb->ieee_registration, - CEA861_OUI_REGISTRATION_ID_HDMI, - sizeof(ctb->ieee_registration))) - tx->connection_type = CONNECTION_TYPE_HDMI; + extensions = (struct edid_extension *) edid + sizeof(block0); + + for (i = 0; i < block0.extensions; i++) { + extension = &extensions[i]; + + switch (extension->tag) { + case EDID_EXTENSION_CEA: + siihdmi_parse_cea861_timing_block(tx, extension); + break; + default: + break; } - break; - default: - break; } + +out: + kfree(edid); } static inline unsigned long siihdmi_ps_to_hz(const unsigned long ps) diff --git a/include/linux/cea861.h b/include/linux/cea861.h index 62aa8b1b40c..bfed94f521b 100644 --- a/include/linux/cea861.h +++ b/include/linux/cea861.h @@ -29,27 +29,59 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/* Header file for EIA CEA 861-E structures and definitions */ + #ifndef LINUX_CEA861_H #define LINUX_CEA861_H -/* Header file for EIA CEA 861-E structures and definitions */ - #include -#define CEA861_DTD_NOT_PRESENT (0x04) -/* NOTE: This is placed inot the EDID in little-endian order */ -static const u8 CEA861_OUI_REGISTRATION_ID_HDMI[] = { 0x00, 0x0C, 0x03 }; +#if !defined(__LITTLE_ENDIAN_BITFIELD) +#warning "structures defined and packed for little-endian byte order" +#endif -struct __packed cea_timing_block { + +#define CEA81_NO_DTDS_PRESENT (0x04) + +static const u8 CEA861_OUI_REGISTRATION_ID_HDMI_LSB[] = { 0x03, 0x0C, 0x00 }; + + +enum cea861_data_block_type { + CEA861_DATA_BLOCK_TYPE_RESERVED0, + CEA861_DATA_BLOCK_TYPE_AUDIO, + CEA861_DATA_BLOCK_TYPE_VIDEO, + CEA861_DATA_BLOCK_TYPE_VENDOR_SPECIFIC, + CEA861_DATA_BLOCK_TYPE_SPEAKER_ALLOCATION, + CEA861_DATA_BLOCK_TYPE_VESA_DTC, + CEA861_DATA_BLOCK_TYPE_RESERVED6, + CEA861_DATA_BLOCK_TYPE_EXTENDED, +}; + + +struct __packed cea861_data_block_header { + unsigned length : 5; + unsigned tag : 3; +}; + +struct __packed cea861_vendor_specific_data_block { + struct cea861_data_block_header header; + + u8 extended_tag; + u8 ieee_registration[3]; + u8 data[]; +}; + + +struct __packed cea861_timing_block { /* CEA Extension Header */ u8 tag; u8 revision; - u8 dtd_start_offset; + u8 dtd_offset; /* Global Declarations */ #if defined(__LITTLE_ENDIAN_BITFIELD) - unsigned dtd_block_count : 4; + unsigned native_dtds : 4; unsigned yuv_422_supported : 1; unsigned yuv_444_supported : 1; unsigned basic_audio_supported : 1; @@ -59,46 +91,58 @@ struct __packed cea_timing_block { unsigned basic_audio_supported : 1; unsigned yuv_444_supported : 1; unsigned yuv_422_supported : 1; - unsigned dtd_block_count : 4; + unsigned native_dtds : 4; #endif - /* CEA Data Block Collection */ - /* Video Data Block */ - u8 video_data_start_offset; - u8 short_video_descriptor[7]; - - /* Audio Data Block */ - u8 audio_data_start_offset; - u8 audio_format; - u8 frequency; - u8 bit_rate; - - /* Speaker Allocation Block */ - u8 speaker_allocation_start_offset; - u8 speaker_designation; - u8 reserved[2]; - - /* Vendor Specific Data Block */ - u8 vendor_specific_offset; - u8 ieee_registration[3]; - u8 component_source[2]; - - struct edid_detailed_timing_descriptor dtb[4]; - - u8 padding[29]; + u8 data[123]; u8 checksum; }; -#if !defined(__LITTLE_ENDIAN_BITFIELD) -#warning "structures defined and packed for little-endian byte order" -#endif - /* HDMI Constants and Structures */ + #define HDMI_PACKET_TYPE_INFO_FRAME (0x80) #define HDMI_PACKET_CHECKSUM (0x100) +struct __packed hdmi_vsdb { + struct cea861_data_block_header header; + + u8 ieee_registration_id[3]; + unsigned port_configuration_a : 4; + unsigned port_configuration_b : 4; + unsigned port_configuration_c : 4; + unsigned port_configuration_d : 4; + + /* extension fields */ + unsigned dvi_dual_link : 1; + unsigned : 1; + unsigned : 1; + unsigned yuv_444_supported : 1; + unsigned colour_depth_30_bit : 1; + unsigned colour_depth_36_bit : 1; + unsigned colour_depth_48_bit : 1; + unsigned audio_info_supported : 1; + + u8 max_tmds_clock; + + unsigned : 1; + unsigned : 1; + unsigned : 1; + unsigned : 1; + unsigned : 1; + unsigned : 1; + unsigned interlaced_latency_fields : 1; + unsigned latency_fields : 1; + + u8 video_latency; + u8 audio_latency; + u8 interlaced_video_latency; + u8 interlaced_audio_latency; + + u8 reserved[]; +}; + /* InfoFrame type constants */ enum info_frame_type { INFO_FRAME_TYPE_RESERVED, diff --git a/include/linux/edid.h b/include/linux/edid.h index a706d5b3729..57bb5405cf3 100644 --- a/include/linux/edid.h +++ b/include/linux/edid.h @@ -38,7 +38,8 @@ #define EDID_BLOCK_SIZE (0x80) #define EDID_MAX_EXTENSIONS (0xfe) -#define EDID_MAGIC (0x00ffffffffffff00) + +static const u8 EDID_HEADER[8] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; enum edid_extension_type { @@ -198,7 +199,7 @@ struct __packed edid_block0 { u8 header[8]; /* vendor/product identification */ - struct { + struct __packed { unsigned id0 : 5; unsigned id1 : 5; unsigned id2 : 5; @@ -215,7 +216,7 @@ struct __packed edid_block0 { u8 revision; /* basic display parameters and features */ - struct { + struct __packed { unsigned dfp_1x_vsync_serration : 1; /* VESA DFP 1.x */ unsigned green_video_sync : 1; unsigned composite_sync : 1; @@ -227,9 +228,10 @@ struct __packed edid_block0 { u8 maximum_horizontal_image_size; /* cm */ u8 maximum_vertical_image_size; /* cm */ + u8 display_transfer_characteristics; /* gamma = (value + 100) / 100 */ - struct { + struct __packed { unsigned default_gtf : 1; /* generalised timing formula */ unsigned preferred_timing_mode : 1; unsigned standard_default_color_space : 1; @@ -260,7 +262,7 @@ struct __packed edid_block0 { u8 white_y; /* established timings */ - struct { + struct __packed { unsigned timing_800x600_60 : 1; unsigned timing_800x600_56 : 1; unsigned timing_640x480_75 : 1; @@ -279,13 +281,13 @@ struct __packed edid_block0 { unsigned timing_800x600_72 : 1; } established_timings; - struct { + struct __packed { unsigned reserved : 7; unsigned timing_1152x870_75 : 1; } manufacturer_timings; /* standard timing id */ - struct { + struct __packed { u8 horizontal_active_pixels; /* = (value + 31) * 8 */ unsigned refresh_rate : 6; /* = value + 60 */