/* * Copyright (C) 2012-2014 Apple Inc. All rights reserved. * * This document is the property of Apple Inc. * It is considered confidential and proprietary. * * This document may not be reproduced or transmitted in any form, * in whole or in part, without the express written permission of * Apple Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hdmi.h" #include "regs.h" #include #include ///////////////////////////////////////// ////////// debug support #define HDMI_DEBUG_MASK ( \ HDMI_DEBUG_INIT | \ HDMI_DEBUG_ERROR | \ HDMI_DEBUG_INFO | \ HDMI_DEBUG_INT | \ HDMI_DEBUG_VIDEO | \ HDMI_DEBUG_PLL | \ HDMI_DEBUG_REG | \ 0) #undef HDMI_DEBUG_MASK #define HDMI_DEBUG_MASK (HDMI_DEBUG_INIT | HDMI_DEBUG_ERROR) #define HDMI_DEBUG_INIT (1<<16) // initialisation #define HDMI_DEBUG_ERROR (1<<17) // errors #define HDMI_DEBUG_INFO (1<<18) // info #define HDMI_DEBUG_INT (1<<19) // interrupts #define HDMI_DEBUG_VIDEO (1<<20) // video #define HDMI_DEBUG_REG (1<<22) // register #define HDMI_DEBUG_I2C (1<<23) // i2c read/write #define HDMI_DEBUG_PLL (1<<24) // PLL #define HDMI_DEBUG_ALWAYS (1<<31) // unconditional output #define debug(_fac, _fmt, _args...) \ do { \ if ((HDMI_DEBUG_ ## _fac) & (HDMI_DEBUG_MASK | HDMI_DEBUG_ALWAYS)) \ dprintf(DEBUG_CRITICAL, "HDMI: %s, %d: " _fmt, __FUNCTION__, __LINE__, ##_args);\ } while(0) ///////////////////////////////////////// ////////// consts, macros #define HDMI_DTPATH "arm-io/hdmi" // Crude screensaver: Turn off hdmi after 3 minutes. #define kScreenBurnTimeout (3 * 60 * 1000000) #define kTMDSMinimumPowerOffIntervalMS 100 #define kHDMICoreResetIntervalMS 1 // Core reset asserted time: Arbitrary large value #define kPHYResetIntervalMS 1 // PHY reset signal high time: Minimum 100 microseconds per HDMI 1.4 PHY manual #define kHDMIStackSize 8192 #define require(assertion, exception_label) \ do { \ if (__builtin_expect(!(assertion), 0)) \ { \ goto exception_label; \ } \ } while (0) #define require_action(assertion, exception_label, action) \ do { \ if (__builtin_expect(!(assertion), 0)) \ { \ { \ action; \ } \ goto exception_label; \ } \ } while (0) #define require_noerr(error_code, exception_label) \ do { \ if (__builtin_expect(0 != (error_code), 0)) \ { \ goto exception_label; \ } \ } while (0) #define require_noerr_action(error_code, exception_label, action) \ do { \ if (__builtin_expect(0 != (error_code), 0)) \ { \ { \ action; \ } \ goto exception_label; \ } \ } while (0) ///////////////////////////////////////// ////////// typedefs, enums, structs uint32_t _registers[] = { 0x00000000, 0x00010000, 0x00030000, 0x00040000, 0x00050000, 0x00060000, 0x00070000, 0x00017000, 0x00080000, }; uint32_t _phyConfigRegs[kPHYConfigRegCount/4 + 1] = { HDMI_BASE_ADDR + HDMIPHYCON0, HDMI_BASE_ADDR + HDMIPHYCON1, HDMI_BASE_ADDR + HDMIPHYCON2, HDMI_BASE_ADDR + HDMIPHYCON3, HDMI_BASE_ADDR + HDMIPHYCON4, HDMI_BASE_ADDR + HDMIPHYCON5, HDMI_BASE_ADDR + HDMIPHYCON6, HDMI_BASE_ADDR + HDMIPHYCON7, HDMI_BASE_ADDR + HDMIPHYCON8, }; enum Module { kCtrlModule = 0, // CTRL_BASE: Controller kHDMIModule = 1, // HDMI_CORE_BASE: HDMI Core kSPDIFModule = 2, // SPDIF_BASE: SPDIF Receiver kI2SModule = 3, // I2S_BASE: I2S Receiver kTGModule = 4, // TG_BASE: Timing Generator kEFuseModule = 5, // EFUSE_BASE: E-Fuse kCECModule = 6, // CEC_BASE: HDMI CEC kHDCPModule = 7, // HDCP Registers kI2CModule = 8, // I2C_BASE: I2C to HDMI PHY kModuleCount = 9 }; ///////////////////////////////////////// ////////// local variables static const char hdmi_dt_path[] = HDMI_DTPATH; static addr_t __base_address; static AppleSamsungHDMITXPHYConfigTableEntry *hdmi_port_phy_cfg_table; size_t hdmi_port_phy_cfg_table_size; const HDMITXPHYConfig *phyConfig; uint32_t frame_interval_MS; struct video_link_data _current_video_link; static bool hdmi_started; static bool hdmi_video_started; static u_int8_t hdmi_controller_type; static u_int8_t hdmi_controller_mode; static struct task_event hdmi_controller_task_event; static struct callout hdmi_controller_screensaver_callout; static bool hdmi_interrupt_occurred; static uint32_t ctrl_int_status; static uint32_t iic_int_status; ///////////////////////////////////////// ////////// local functions declaration static int hdmi_controller_task(void *arg); static int hdmi_phy_regs_task(void *arg); static int configure_video(struct video_link_data *data, uint32_t pixel_clock); static void init_video(bool master); static void configure_video_mute(struct video_link_data *data, bool mute); static int configure_video_color(const struct video_link_data * data); static int configure_video_mode(struct video_link_data *data, uint32_t pixel_clock); static void configure_video_bist(struct video_link_data *data); static void hdmi_interrupt_filter(void *arg); static void handle_interrupt(void); static void uninit_video(); static int reset(void); static void power_down_phy(bool fullPowerDown); static int validate_video_link(struct video_link_data * data, uint32_t pixel_clock, const HDMITXPHYConfig ** pPHYConfig); static int set_tx_output_enabled(bool enabled); static int set_tx_output_mode(int mode); static void handle_screensaver_callout(struct callout *co, void *args); void set_av_mute_enabled(bool enabled); static uint8_t read_reg(uint8_t module, u_int32_t offset); static uint32_t read_reg32(uint8_t module, u_int32_t offset); static void write_reg(uint8_t module, u_int32_t offset, uint8_t val); static void write_reg32(uint8_t module, u_int32_t offset, uint32_t val); static void and_reg(uint8_t module, u_int32_t offset, uint8_t val); static void or_reg(uint8_t module, u_int32_t offset, uint8_t val); static void set_bits_in_reg(uint8_t module, u_int32_t offset, uint8_t pos, uint8_t mask, uint8_t value); static uint32_t get_bits_in_reg(uint8_t module, uint32_t reg, uint32_t pos, uint32_t mask); static uint8_t read_phy_reg(uint8_t offset); static int write_phy_reg(u_int32_t offset, const uint8_t data); static void or_phy_reg(u_int32_t offset, uint8_t data); static void and_phy_reg(u_int32_t offset, uint8_t data); void write_regs(uint8_t module, uint32_t basereg, uint32_t, unsigned int length); void write_regs_with_data(uint8_t module, uint32_t basereg, uint8_t * data, unsigned int length); static int write_phy_config_reg(PHYConfigReg reg, uint32_t data); static int stop_video_link(); static int complete_video_link(); static void set_TMDS_power_enabled(bool enabled); static int bypass_wait = 0; struct hdmi_controller_config { addr_t base_address; u_int32_t clock; u_int32_t irq; const char *dt_path; }; static const struct hdmi_controller_config hdmi_config[] = { #if WITH_HW_DISPLAY_HDMI { HDMI_BASE_ADDR, CLK_HDMI, INT_HDMI_LINK, hdmi_dt_path }, #else { 0, 0, 0, 0 }, #endif // WITH_HW_DISPLAY_HDMI }; struct display_infoframe aviInfoFrame = { kDisplayInfoFrameTypeAVI, 0x02, //Version 0x0d, //length 0xf3, //checksum {0x10, 0x68, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 } //data }; extern AppleSamsungHDMITXPHYConfigTableEntry hdmi_static_config[]; extern size_t hdmi_static_config_size; ///////////////////////////////////////// ////////// controller global functions int hdmi_controller_start(u_int8_t type, u_int8_t mode) { u_int32_t hpdstate; if (hdmi_started) return 0; hdmi_controller_type = type; hdmi_controller_mode = mode; __base_address = hdmi_config[hdmi_controller_type].base_address; // register interrupt handler install_int_handler(hdmi_config[hdmi_controller_type].irq, hdmi_interrupt_filter, NULL); event_init(&hdmi_controller_task_event, EVENT_FLAG_AUTO_UNSIGNAL, false); hdmi_port_phy_cfg_table = (AppleSamsungHDMITXPHYConfigTableEntry *)hdmi_static_config; hdmi_port_phy_cfg_table_size = hdmi_static_config_size; clock_gate(hdmi_config[hdmi_controller_type].clock, true); unmask_int(hdmi_config[hdmi_controller_type].irq); reset(); //power down HDMI PHY, keeping HPD enabled power_down_phy(false); hdmi_started = true; task_start(task_create("hdmi", &hdmi_controller_task, NULL, kHDMIStackSize)); hpdstate = read_reg(kCtrlModule, CTRL_HPD_STATUS); debug(INIT,"hpdstate=0x%08x\n", hpdstate); if (hpdstate == 0x00000001) { hdmi_device_start(); } // clear any pending HPD interrupts (Note: status bits are write-one-to-clear) write_reg(kCtrlModule, CTRL_INTC_FLAG, CTRL_INTC_FLAG_HPD_CHG); // unmask HPD or_reg(kCtrlModule, CTRL_INTC_CON, CTRL_INTC_CON_EN_GLOBAL|CTRL_INTC_CON_EN_HPD_CHG); write_reg(kCtrlModule, CTRL_INTC_FLAG, CTRL_INTC_FLAG_HPD_CHG); return 0; } void hdmi_controller_stop() { if (!hdmi_started) { debug(INT, "hdmi not started. Nothing to do\n"); return; } hdmi_controller_stop_video(); // disable interrupt source mask_int(hdmi_config[hdmi_controller_type].irq); // Stop background EDID polling. abort_edid(); hdmi_video_started = false; hdmi_device_stop(); if (hdmi_controller_type == kHDMIControllerType_HDMI) { callout_dequeue(&hdmi_controller_screensaver_callout); } hdmi_started = false; } int hdmi_controller_validate_video(struct video_timing_data *timings) { uint64_t pixel_clock; // Assume basic settings. struct video_link_data data; bzero(&data, sizeof(data)); bcopy(timings, &data.timing, sizeof(*timings)); data.mirror_mode = false; data.test_mode = 0; data.color.depth = 8; data.color.space = kDisplayColorSpacesRGB; data.color.range = kDisplayColorDynamicRangeVESA; data.color.coefficient = kDisplayColorCoefficientITU601; pixel_clock = data.timing.axis[kDisplayAxisTypeVertical].total * data.timing.axis[kDisplayAxisTypeHorizontal].total; pixel_clock *= data.timing.axis[kDisplayAxisTypeVertical].sync_rate; // vTotal * hTotal * vSyncRate pixel_clock >>= 16; debug(VIDEO, "stream clock=%lld\n", pixel_clock); return validate_video_link(&data, pixel_clock, NULL); } int hdmi_controller_read_bytes_i2c(u_int32_t device_addr, u_int8_t addr, u_int8_t *data, u_int32_t length) { uint8_t offset[1]; offset[0] = addr; return (iic_read(HDMI_DDC_IIC_BUS, device_addr, offset, 1, data, length, IIC_COMBINED)); } int hdmi_controller_write_bytes_i2c(u_int32_t device_addr, u_int32_t addr, u_int8_t *data, u_int32_t length) { return -1; } int hdmi_controller_start_video(struct video_link_data *data, uint32_t pixel_clock) { struct video_link_data edid_data; struct video_link_data *timings = NULL; if ( !hdmi_started ) return -1; debug(INIT,"Pixel clock %d\n", pixel_clock); // Overwriting timings with EDID preference. bcopy(data, &edid_data, sizeof(edid_data)); if (get_edid_timings(&edid_data.timing) == 0) timings = &edid_data; if (timings == NULL) timings = data; return configure_video(timings, pixel_clock); } int hdmi_controller_stop_video() { if ( !hdmi_video_started ) return -1; stop_video_link(); complete_video_link(); // unmask HPD or_reg(kCtrlModule, CTRL_INTC_CON, CTRL_INTC_CON_EN_GLOBAL|CTRL_INTC_CON_EN_HPD_CHG); write_reg(kCtrlModule, CTRL_INTC_FLAG, CTRL_INTC_FLAG_HPD_CHG); hdmi_video_started = false; return 0; } bool hdmi_controller_video_configured() { return hdmi_video_started; } uint8_t hdmi_read_reg(uint32_t offset) { return( read_reg(0, offset) ); } void hdmi_write_reg(uint32_t offset, uint8_t value) { write_reg(0, offset, value); } ///////////////////////////////////////// ////////// controller local functions static void handle_screensaver_callout(struct callout *co, void *args) { debug(ALWAYS, "HDMI Screensaver\n"); // set_TMDS_power_enabled calls write_phy_reg which endup calling event_wait_timeout. Such call is not safe when performed in the handling of the callout. bypass_wait = 1; set_TMDS_power_enabled(false); bypass_wait = 0; } static int hdmi_controller_task(void *arg) { if (hdmi_controller_type == kHDMIControllerType_HDMI) { callout_enqueue(&hdmi_controller_screensaver_callout, kScreenBurnTimeout, handle_screensaver_callout, NULL); } // Perform this on the hdmi task to prevent blocking startup. for (;;) { event_wait(&hdmi_controller_task_event); // if controller was stopped before hdmi task got chance to run, exit if (hdmi_started == false) { debug(ERROR, "controller task stopped since hdmi stopped\n"); return 0; } mask_int(hdmi_config[hdmi_controller_type].irq); if (hdmi_interrupt_occurred) { hdmi_interrupt_occurred = false; if (hdmi_controller_type == kHDMIControllerType_HDMI) { callout_reset(&hdmi_controller_screensaver_callout, 0); } // Invoke bottom half. handle_interrupt(); } unmask_int(hdmi_config[hdmi_controller_type].irq); } return 0; } static bool get_hot_plug_detect() { return read_reg(kCtrlModule, CTRL_HPD_STATUS) & CTRL_HPD_STATUS_PLUGGED; } static void hdmi_interrupt_filter(void *arg) { ctrl_int_status = read_reg(kCtrlModule, CTRL_INTC_FLAG); write_reg(kCtrlModule, CTRL_INTC_FLAG, ctrl_int_status); iic_int_status = read_reg32(kI2CModule, IICINT); write_reg32(kI2CModule, IICINT, iic_int_status); debug(INT, "Interrupt received. ctrl_int_status=0x%08x\n", ctrl_int_status); debug(INT, "Interrupt received. ctrl_int_status=0x%08x iic_int_status=0x%08x\n", ctrl_int_status, iic_int_status); hdmi_interrupt_occurred = true; event_signal(&hdmi_controller_task_event); } static void handle_interrupt(void) { // Handle unplug event if ( (ctrl_int_status & CTRL_INTC_FLAG_HPD_UNPLUG) ) { debug(INT, "HPD_UNPLUG\n"); debug(INT, "MUST HANDLE TEAR DOWN\n"); hdmi_device_stop(); } // Handle plug event if ( (ctrl_int_status & CTRL_INTC_FLAG_HPD_PLUG) ) { debug(INT, "HPD_PLUG\n"); if ( get_hot_plug_detect() ) { debug(INT, "MUST HANDLE PLUG IN \n"); hdmi_device_start(); } else { debug(INT, "MUST HANDLE UNPLUG\n"); hdmi_device_stop(); } } // Handle HDCP interrupts if ((ctrl_int_status & CTRL_INTC_FLAG_HDCP)) { uint32_t hdcp_int_status; debug(INT, "HDCP REALLY!!!\n"); hdcp_int_status = read_reg(kHDMIModule, HDMI_STATUS); // Clear active HDCP interrupts (Note: bit 7 is read-only and not an interrupt source) write_reg(kHDMIModule, HDMI_STATUS, hdcp_int_status); } } static void reset_HDMI_core(void) { // From H4I Power Manager (PMGR) Microarchitecture Specification // section 7.2 Block Resets (see H4i_UM): // // NOTE: Some of the blocks may have CSR bits defined as reset bits, however some of these have // been disabled. The only valid supported block reset mechanism in H4I is the reset bit in the // block's power state register. //Reset device clock_reset_device(hdmi_config[hdmi_controller_type].clock); } static void reset_PHY(void) { // Ensure that PHY is powered on (if the PHY power off signal is connected) and_reg(kCtrlModule, CTRL_PHY_CON_0, ~CTRL_PHY_CON_0_PHY_PWR_OFF); // Reset PHY or_reg(kCtrlModule, CTRL_PHY_RSTOUT, CTRL_HDMI_PHY_RSTOUT_RESET); task_sleep(kPHYResetIntervalMS); and_reg(kCtrlModule, CTRL_PHY_RSTOUT, ~(CTRL_HDMI_PHY_RSTOUT_RESET)); } static void set_TMDS_power_enabled(bool enabled) { uint8_t reg; uint8_t mask; static bool tmds_enabled = 0; if (tmds_enabled == enabled) return; tmds_enabled = enabled; reg = PHY_CONFIG_REG1D; mask = PHY_V1P4_CONFIG_REG1D_I2C_PDEN | PHY_V1P4_CONFIG_REG1D_TX_DRV_PD; if (enabled ) { and_phy_reg(reg, ~mask); } else { or_phy_reg(reg, mask); } } static void power_down_phy(bool fullPowerDown) { or_phy_reg(PHY_CONFIG_REG1D, PHY_V1P4_CONFIG_REG1D_I2C_PDEN | (fullPowerDown ? PHY_V1P4_CONFIG_REG1D_FULL_PD : PHY_V1P4_CONFIG_REG1D_PD_WITH_HPD)); debug(INIT,"PHY %s power down\n", fullPowerDown ? "full" : "partial"); if ( fullPowerDown ) { // Power down PHY (if the PHY power off signal is connected) or_reg(kCtrlModule, CTRL_PHY_CON_0, CTRL_PHY_CON_0_PHY_PWR_OFF); } } bool get_is_PHY_ready(void) { return ((read_reg(kCtrlModule, CTRL_PHY_STATUS_0) & CTRL_PHY_STATUS_0_PHY_READY) ? true : false) ; } static int reset(void) { debug(INIT,"PHY_CON_0=0x%08x\n", read_reg(kCtrlModule, CTRL_PHY_CON_0)); // Reset HDMI core and PHY debug(INIT,"PHYReady=%u\n", get_is_PHY_ready()); // Reset HDMI core (Note: core reset disables PHY power in case of 1.4 TX) reset_HDMI_core(); // Reset HDMI PHY reset_PHY(); // Set TMDS bit order set_bits_in_reg(kHDMIModule, HDMI_CON_0, 0, HDMI_CON_0_ENCODING_RETAIN_BIT_ORDER, 0); debug(INIT,"PHYReady=%u\n", get_is_PHY_ready()); debug(INIT,"PHY_CON_0=0x%08x\n", read_reg(kCtrlModule, CTRL_PHY_CON_0)); // Initialize the I2C bridge // // - Clear pending interrupts // - Set configuration options from device tree // - Unmask Stop interrupt // - Enable ACK generation // - Set mode to master transmitter and enable I2C output write_reg32(kI2CModule, SW_RESET, kIICSwReset); do { write_reg32(kI2CModule, HDMIPHY_ID, PHY_I2C_ADDRESS); } while (read_reg32(kI2CModule, HDMIPHY_ID) != PHY_I2C_ADDRESS); write_reg32(kI2CModule, IICINT, kIICIntAll); // Clear pending interrupts write_reg32(kI2CModule, IICCON, (IICCONFIG_IICCON | kIICConAckGen)); write_reg32(kI2CModule, FIFOCON, IICCONFIG_FIFOCON); write_reg32(kI2CModule, IICBUSCON, IICCONFIG_IICBUSCON); write_reg32(kI2CModule, TIMEOUT_CON, IICCONFIG_TIMEOUTCON); write_reg32(kI2CModule, IICSTAT, (kIICStatMaster | kIICStatTx | kIICStatSOE)); return 0; } static int configure_video(struct video_link_data *data, uint32_t pixel_clock) { debug(INIT,"starting to configure video\n"); if (hdmi_video_started) return 0; // Uncomment to show the timings we finally decide to use. debug(ALWAYS, "configure_video with:\n"); debug(ALWAYS, "mirror_mode: %d\n", data->mirror_mode); debug(ALWAYS, "test_mode: 0x%08x\n", data->test_mode); debug(ALWAYS, "color:\n"); debug(ALWAYS, " depth: %u\n", data->color.depth); debug(ALWAYS, " space: %u\n", data->color.space); debug(ALWAYS, " range: %u\n", data->color.range); debug(ALWAYS, " coefficient: %u\n", data->color.coefficient); debug(ALWAYS, "timing:\n"); debug(ALWAYS, " interlaced: %d\n", data->timing.interlaced); { int i; for (i = 0; i < kDisplayAxisCount; ++i) { debug(ALWAYS, " Axis %d:\n", i); debug(ALWAYS, " total: %u\n", data->timing.axis[i].total); debug(ALWAYS, " active: %u\n", data->timing.axis[i].active); debug(ALWAYS, " sync_width: %u\n", data->timing.axis[i].sync_width); debug(ALWAYS, " back_porch: %u\n", data->timing.axis[i].back_porch); debug(ALWAYS, " front_porch: %u\n", data->timing.axis[i].front_porch); debug(ALWAYS, " sync_rate: %u (%uHz)\n", data->timing.axis[i].sync_rate, data->timing.axis[i].sync_rate >> 16); debug(ALWAYS, " sync_polarity: %u\n", data->timing.axis[i].sync_polarity); } } init_video((data->mirror_mode ? false : true)); if ( configure_video_mode(data, pixel_clock) != 0 ) { debug(ERROR,"failed configure_video_mode\n"); goto exit; } if ( configure_video_color(data) != 0 ) goto exit; configure_video_bist(data); configure_video_mute(data, false); hdmi_video_started = true; debug(VIDEO, "finished configuring video\n"); return 0; exit: debug(ERROR, "failed to configure video\n"); hdmi_video_started = false; return -1; } static void init_video(bool master) { debug(INIT,"%s:nothing to do yet\n", __FUNCTION__); } static HDMITXPHYConfigDepth phy_config_depth(uint32_t depth) { HDMITXPHYConfigDepth depthIndex; switch ( depth ) { case 12: depthIndex = kHDMITXPHYConfigDepth12; break; case 10: depthIndex = kHDMITXPHYConfigDepth10; break; default: depthIndex = kHDMITXPHYConfigDepth8; break; } return depthIndex; } const HDMITXPHYConfig * find_PHY_config(uint32_t pixelClockHz, uint32_t pixelClockMinHz, uint32_t pixelClockMaxHz, uint32_t colorDepth) { const HDMITXPHYConfig * bestConfig = NULL; const HDMITXPHYConfigDepth depthIndex = phy_config_depth(colorDepth); if ( hdmi_port_phy_cfg_table ) { const AppleSamsungHDMITXPHYConfigTableEntry * table; unsigned int rowCount; uint32_t bestError; // Search for an entry with a matching pixel clock // // Note: The table must be sorted by pixel clock in ascending order. // table = (const AppleSamsungHDMITXPHYConfigTableEntry *)hdmi_port_phy_cfg_table; rowCount = hdmi_port_phy_cfg_table_size / sizeof table[0]; bestError = UINT32_MAX; for (unsigned int i = 0; i < rowCount; i++) { uint32_t configPixelClockHz = table[i].pixelClockHz; uint32_t error; debug(INFO,"pixelClockMaxHz %d pixel_clock %d configPixelClockHz %d\n", pixelClockMaxHz, pixelClockHz, configPixelClockHz); if (configPixelClockHz < pixelClockMinHz) { goto next; } if (configPixelClockHz > pixelClockMaxHz) { goto exit; } if (table[i].depth[depthIndex].invalid) { goto next; } if ( configPixelClockHz < pixelClockHz ) { error = pixelClockHz - configPixelClockHz; } else { error = configPixelClockHz - pixelClockHz; } if ( error < bestError ) { bestError = error; bestConfig = &table[i].depth[depthIndex].data; } next: continue; } } exit: return bestConfig; } static void configure_video_mute(struct video_link_data *data, bool mute) { set_av_mute_enabled(mute); } static int validate_video_color(const struct video_color_data * color) { int ret = 0; if (color->depth < 8 || color->depth > 12) { ret = -1; debug(ERROR,"Color depth not supported\n"); goto exit; } if (color->space == kDisplayColorSpacesYCbCr422) { // It is invalid to have a depth other than 8 with YCbCr4:2:2 [see HDMI v1.3 section 6.5]. if (color->depth != 8) { ret = -1; debug(ERROR,"YCbCr4:2:2 requires depth = 8 bits\n"); goto exit; } } exit: return ret; } static int validate_video_timing(const struct video_timing_axis * h, const struct video_timing_axis * v) { int ret = 0; if (h->total <= 0 || h->total > kHorizontalTotalLimit) { ret = -1; goto exit; } if (v->total <= 0 && v->total > kVerticalTotalLimit) { ret = -1; goto exit; } if (h->active > kHorizontalActiveLimit) { ret = -1; goto exit; } if (v->active > kVerticalActiveLimit) { ret = -1; goto exit; } if ((h->total < h->active) || ((h->total - h->active) > kHorizontalBlankLimit)) { ret = -1; goto exit; } if ((v->total < v->active) || ((v->total - v->active) > kVerticalBlankLimit)) { ret = -1; goto exit; } exit: return ret; } #define kVideoPixelClkTolleranceNumerator 5 #define kVideoPixelClkTolleranceDenomiator 1000 static inline u_int32_t ulmin(u_int32_t a, u_int32_t b) { return (a < b ? a : b); } uint32_t _pixelClockLimit = UINT32_MAX; static int validate_video_link(struct video_link_data * data, uint32_t pixel_clock, const HDMITXPHYConfig ** pPHYConfig) { const struct video_timing_axis * h = &data->timing.axis[kDisplayAxisTypeHorizontal]; const struct video_timing_axis * v = &data->timing.axis[kDisplayAxisTypeVertical]; const struct video_color_data * color = &data->color; const HDMITXPHYConfig * phyConfig = NULL; uint32_t pixelClockToleranceHz, pixelClockMinHz, pixelClockMaxHz; int64_t pixelClockToleranceFixed = pixel_clock; int ret = 0; pixelClockToleranceFixed *= kVideoPixelClkTolleranceNumerator; pixelClockToleranceFixed /= kVideoPixelClkTolleranceDenomiator; pixelClockToleranceHz = (uint32_t) pixelClockToleranceFixed; // Uncomment to show the timings we finally decide to use. debug(VIDEO,"Validating link\n"); debug(VIDEO,"mirror_mode: %d\n", data->mirror_mode); debug(VIDEO,"test_mode: 0x%08x\n", data->test_mode); debug(VIDEO,"color:\n"); debug(VIDEO," depth: %u\n", data->color.depth); debug(VIDEO," space: %u\n", data->color.space); debug(VIDEO," range: %u\n", data->color.range); debug(VIDEO," coefficient: %u\n", data->color.coefficient); debug(VIDEO,"timing:\n"); debug(VIDEO," interlaced: %d\n", data->timing.interlaced); { int i; for (i = 0; i < kDisplayAxisCount; ++i) { debug(VIDEO," Axis %d:\n", i); debug(VIDEO," total: %u\n", data->timing.axis[i].total); debug(VIDEO," active: %u\n", data->timing.axis[i].active); debug(VIDEO," sync_width: %u\n", data->timing.axis[i].sync_width); debug(VIDEO," back_porch: %u\n", data->timing.axis[i].back_porch); debug(VIDEO," front_porch: %u\n", data->timing.axis[i].front_porch); debug(VIDEO," sync_rate: %u (%uHz)\n", data->timing.axis[i].sync_rate, data->timing.axis[i].sync_rate >> 16); debug(VIDEO," sync_polarity: %u\n", data->timing.axis[i].sync_polarity); } } // Interlaced video not supported if (data->timing.interlaced) { ret = -1; debug(VIDEO,"interlaced mode not supported\n"); goto exit; } // Mirror mode is not supported if (data->mirror_mode) { ret = -1; debug(VIDEO,"Mirror mode not supported\n"); goto exit; } // Verify that color mode is supported ret = validate_video_color(color); if (ret < 0) { goto exit; } // Calculate target pixel clock range, clamping max value to _pixelClockLimit pixelClockMinHz = pixel_clock - pixelClockToleranceHz; pixelClockMaxHz = ulmin(pixel_clock + pixelClockToleranceHz, _pixelClockLimit); // Verify that the target pixel clock is within the valid range if (pixel_clock > _pixelClockLimit) { ret = -1; debug(VIDEO,"pixel clock value is too high, %lu\n", (unsigned long)pixel_clock); goto exit; } // Verify that there is a PHY configuration for the pixel clock phyConfig = find_PHY_config(pixel_clock, pixelClockMinHz, pixelClockMaxHz, data->color.depth); if (phyConfig == NULL) { ret = -1; debug(VIDEO,"No PHY configuration for required pixel clock: %lu\n", (unsigned long)pixel_clock); goto exit; } // Return the PHY configuration if requested if ( pPHYConfig ) { *pPHYConfig = phyConfig; } // Verify that timing generator parameters are supported ret = validate_video_timing(h, v); if (ret < 0) { debug(VIDEO,"Timing not supported\n"); goto exit; } exit: debug(ERROR,"ret=0x%08x\n", ret); return ret; } void set_horizontal_blanking(uint32_t value) { // H_BLANK_0:1 write_regs(kHDMIModule, HDMI_H_BLANK_0, value, 2); } void set_vertical_blanking(uint32_t blanking, uint32_t total) { // V1_BLANK_0:1 write_regs(kHDMIModule, HDMI_V1_BLANK_0, blanking, 2); // V2_BLANK_0:1 write_regs(kHDMIModule, HDMI_V2_BLANK_0, total, 2); } void set_line_lengths(uint32_t h_line, uint32_t v_line) { debug(VIDEO,"h_line %d, v_line %d\n", h_line, v_line); // V_LINE_0:1 write_regs(kHDMIModule, HDMI_V_LINE_0, v_line, 2); // H_LINE_0:1 write_regs(kHDMIModule, HDMI_H_LINE_0, h_line, 2); } void set_bottom_field_extents(uint32_t start, uint32_t end) { // V_BLANK_F0_0:1 write_regs(kHDMIModule, HDMI_V_BLANK_F0_0, start, 2); // V_BLANK_F1_0:1 write_regs(kHDMIModule, HDMI_V_BLANK_F1_0, end, 2); } void set_horizontal_sync_shape(bool active_high, uint32_t start, uint32_t end) { // HSYNC_POL write_reg(kHDMIModule, HDMI_HSYNC_POL, !active_high); // H_SYNC_START_0:1 write_regs(kHDMIModule, HDMI_H_SYNC_START_0, start, 2); // H_SYNC_END_0:1 write_regs(kHDMIModule, HDMI_H_SYNC_END_0, end, 2); } void set_vertical_sync_shape(bool active_high, uint32_t vsync1_start, uint32_t vsync1_end, uint32_t vsync2_start, uint32_t vsync2_end) { // VSYNC_POL write_reg(kHDMIModule, HDMI_VSYNC_POL, !active_high); // V_SYNC_LINE_BEF_1_0:1 - top field vsync start write_regs(kHDMIModule, HDMI_V_SYNC_LINE_BEF_1_0, vsync1_start, 2); // V_SYNC_LINE_BEF_2_0:1 - top field vsync end write_regs(kHDMIModule, HDMI_V_SYNC_LINE_BEF_2_0, vsync1_end, 2); // V_SYNC_LINE_AFT_1_0:1 - bottom field vsync start write_regs(kHDMIModule, HDMI_V_SYNC_LINE_AFT_1_0, vsync2_start, 2); // V_SYNC_LINE_AFT_2_0:1 - bottom field vsync end write_regs(kHDMIModule, HDMI_V_SYNC_LINE_AFT_2_0, vsync2_end, 2); } static void set_bottom_field_transition_point(uint32_t start, uint32_t end) { // V_SYNC_LINE_AFT_PXL_1_0:1 write_regs(kHDMIModule, HDMI_V_SYNC_LINE_AFT_PXL_1_0, start, 2); // V_SYNC_LINE_AFT_PXL_2_0:1 write_regs(kHDMIModule, HDMI_V_SYNC_LINE_AFT_PXL_2_0, end, 2); } static int configure_timing_generator(const struct video_link_data * data) { const struct video_timing_axis * h = &data->timing.axis[kDisplayAxisTypeHorizontal]; const struct video_timing_axis * v = &data->timing.axis[kDisplayAxisTypeVertical]; uint32_t h_blanking = h->total - h->active; uint32_t v_blanking = v->total - v->active; uint32_t v_bot_start = v->total + v_blanking + 1; // interlaced only // Make "line 1" coincident with the first VSYNC, as in CEA formats uint32_t vsync1_lineno = 1; uint32_t vsync2_lineno = vsync1_lineno + v->total; // interlaced only // Configure Timing Generator related registers // TG_CMD write_reg(kTGModule, TG_CMD, data->timing.interlaced ? TG_CMD_FIELD_EN : 0); // TG_CBGEN // Pixel repititon is not supported // TG_H_FSZ_L:TG_H_FSZ_H write_regs(kTGModule, TG_H_FSZ_L, h->total, 2); // TG_HACT_ST_L:TG_HACT_ST_H write_regs(kTGModule, TG_HACT_ST_L, h_blanking, 2); // TG_HACT_SZ_L:TG_HACT_SZ_H write_regs(kTGModule, TG_HACT_SZ_L, h->active, 2); // TG_V_FSZ_L:TG_V_FSZ_H write_regs(kTGModule, TG_V_FSZ_L, v->total, 2); // TG_VSYNC_L:TG_VSYNC_H write_regs(kTGModule, TG_VSYNC_L, vsync1_lineno, 2); // TG_VSYNC2_L:TG_VSYNC2_H (interlaced only) write_regs(kTGModule, TG_VSYNC2_L, vsync2_lineno, 2); // TG_VACT_ST_L:TG_VACT_ST_H write_regs(kTGModule, TG_VACT_ST_L, v_blanking, 2); // TG_VACT_SZ_L:TG_VACT_SZ_H write_regs(kTGModule, TG_VACT_SZ_L, v->active, 2); // TG_FIELD_CHG_L:TG_FIELD_CHG_H (interlaced only) write_regs(kTGModule, TG_FIELD_CHG_L, vsync2_lineno, 2); // TG_VACT_ST2_L:TG_VACT_ST2_H (interlaced only) write_regs(kTGModule, TG_VACT_ST2_L, v_bot_start, 2); // Note: 3D (HDMI 1.4 TX only) not supported // TG_VSYNC_TOP_HDMI_L:TG_VSYNC_TOP_HDMI_H write_regs(kTGModule, TG_VSYNC_TOP_HDMI_L, vsync1_lineno, 2); // same as TG_VSYNC_L:TG_VSYNC_H // TG_VSYNC_BOT_HDMI_L:TG_VSYNC_BOT_HDMI_H (interlaced only) write_regs(kTGModule, TG_VSYNC_BOT_HDMI_L, vsync2_lineno, 2); // same as TG_VSYNC2_L:TG_VSYNC2_H // TG_FIELD_TOP_HDMI_L:TG_FIELD_TOP_HDMI_H write_regs(kTGModule, TG_FIELD_TOP_HDMI_L, vsync1_lineno, 2); // same as TG_VSYNC_L:TG_VSYNC_H // TG_FIELD_BOT_HDMI_L:TG_FIELD_BOT_HDMI_H write_regs(kTGModule, TG_FIELD_BOT_HDMI_L, vsync2_lineno, 2); // same as TG_VSYNC2_L:TG_VSYNC2_H // TG_HDMI_TPGEN // Pixel repititon is not supported return 0; } static int configure_video_format(struct video_link_data * data) { const struct video_timing_axis * h = &data->timing.axis[kDisplayAxisTypeHorizontal]; const struct video_timing_axis * v = &data->timing.axis[kDisplayAxisTypeVertical]; uint32_t h_blanking = h->total - h->active; uint32_t v_blanking = v->total - v->active; uint32_t v_line = data->timing.interlaced ? (v->total * 2 + 1) : v->total; uint32_t v_bot_start = v->total + v_blanking + 1; // interlaced only uint32_t hsync_start = h->front_porch - 2; // There is apparently an implicit +2 added to HSYNC_START (compare manual recommended values to CEA-861-D formats) uint32_t hsync_end = hsync_start + h->sync_width; uint32_t vsync1_start = v->front_porch; uint32_t vsync1_end = vsync1_start + v->sync_width; uint32_t vsync2_start = v->total + vsync1_start; // interlaced only uint32_t vsync2_end = v->total + vsync1_end; // interlaced only // The +0.5 vertical lines offset, not included in vsync2_start/end uint32_t vsync2_h_start = (h->total / 2) + h->front_porch; // interlaced only uint32_t vsync2_h_end = vsync2_h_start; // interlaced only uint64_t const64 = 1000; // Calculate frame interval rounded (half up) to nearest millisecond frame_interval_MS = (((const64 << 32) / v->sync_rate) + 0x8000) >> 16; // Configure registers related to video format set_horizontal_blanking(h_blanking); set_vertical_blanking(v_blanking, v->total); set_line_lengths(h->total, v_line); // INT_PRO_MODE write_reg(kHDMIModule, HDMI_INT_PRO_MODE, data->timing.interlaced); set_bottom_field_extents(v_bot_start, v_line); // interlaced only set_horizontal_sync_shape(h->sync_polarity, hsync_start, hsync_end); set_vertical_sync_shape(v->sync_polarity, vsync1_start, vsync1_end, vsync2_start, vsync2_end); set_bottom_field_transition_point(vsync2_h_start, vsync2_h_end); // HDMI_CON_1[Pxl_rep_num] // Pixel repititon is not supported return 0; } static void set_mute_pixel(struct pixel pixel) { // BLUE_SCREEN_[RGB]_0 - upper half of the least significant 8 bits write_reg(kHDMIModule, HDMI_BLUE_SCREEN_R_0, (pixel.r >> 4) & 0x0F); write_reg(kHDMIModule, HDMI_BLUE_SCREEN_G_0, (pixel.g >> 4) & 0x0F); write_reg(kHDMIModule, HDMI_BLUE_SCREEN_B_0, (pixel.b >> 4) & 0x0F); // BLUE_SCREEN_[RGB]_1 - the most significant 8 bits write_reg(kHDMIModule, HDMI_BLUE_SCREEN_R_1, pixel.r >> 8); write_reg(kHDMIModule, HDMI_BLUE_SCREEN_G_1, pixel.g >> 8); write_reg(kHDMIModule, HDMI_BLUE_SCREEN_B_1, pixel.b >> 8); } static void set_color_space(int space) { // Enable or disable YCbCr4:2:2 mode (RGB4:4:4/YCbCr4:4:4 are handled identically) set_bits_in_reg(kHDMIModule, HDMI_CON_0, 0, HDMI_CON_0_YCBCR422_SEL, space == kDisplayColorSpacesYCbCr422 ? HDMI_CON_0_YCBCR422_SEL : 0); } static int configure_video_pattern(const struct video_link_data * data) { // enable video pattern generation if ( data->test_mode ) { or_reg(kHDMIModule, HDMI_VIDEO_PATTERN_GEN, HDMI_VIDEO_PATTERN_GEN_PATTERN_ENABLE); debug(VIDEO,"Video pattern generation enabled.\n"); } else { and_reg(kHDMIModule, HDMI_VIDEO_PATTERN_GEN, ~(HDMI_VIDEO_PATTERN_GEN_PATTERN_ENABLE)); } return 0; } static int set_tx_output_mode(int mode) { uint32_t dvi_mask = HDMI_CON_2_VID_PERIOD_EN | HDMI_CON_2_DVI_BAND_EN; uint8_t transtaled_mode = (mode == kHDMI_tx_mode_HDMI) ? HDMI_MODE_SEL_MODE_HDMI : HDMI_MODE_SEL_MODE_DVI; debug(VIDEO,"%s %d:, Mode %s\n", __FUNCTION__, __LINE__, (mode == kHDMI_tx_mode_HDMI) ? "HDMI" : "DVI"); // MODE_SEL[1:0] = mode set_bits_in_reg(kHDMIModule, HDMI_MODE_SEL, HDMI_MODE_SEL_MODE_SHIFT, HDMI_MODE_SEL_MODE_MASK, transtaled_mode); // Configure video preamble and guard band // HDMI_CON_2[Vid_Period_En] = 1 if DVI mode, 0 otherwise // HDMI_CON_2[Dvi_Band_En] = 1 if DVI mode, 0 otherwise set_bits_in_reg(kHDMIModule, HDMI_CON_2, 0, dvi_mask, (mode == kHDMI_tx_mode_DVI) ? dvi_mask : 0); return 0; } static inline int set_PHY_config_done(bool done) { write_phy_config_reg(kPHYConfigRegModeSet, done ? PHY_CONFIG_MODE_SET_DONE : PHY_CONFIG_MODE_SET_IN_PROGRESS); return 0; } static int configure_PHY(const HDMITXPHYConfig * config) { uint8_t buf[kPHYConfigRegCount]; int ret = 0; int reg; // Sanity check (if this fails, it is a programming error) if (sizeof (*config) != (sizeof (buf) - 2)) { ret = -1; goto exit; } // Copy config into data buffer; initialize first & last bytes from registers buf[kPHYConfigReg00] = read_phy_reg(kPHYConfigReg00); buf[kPHYConfigReg07] = read_phy_reg(kPHYConfigReg07); memcpy((uint8_t *)buf + 1, config, sizeof (*config)); debug(VIDEO,"PHY_STATUS=0x%08x\n", read_reg(kCtrlModule, CTRL_PHY_STATUS_0)); // Configure the PHY ret = set_PHY_config_done(false); if (ret < 0) { debug(ERROR,"set_PHY_config_done: ret %d\n", ret); goto exit; } for (reg = kPHYConfigReg00; reg != kPHYConfigRegCount; reg++) { ret = write_phy_config_reg(reg, buf[reg]); if (ret < 0) { debug(ERROR,"write_phy_config_reg: ret %d\n", ret); goto exit; } } ret = set_PHY_config_done(true); if (ret < 0) { debug(ERROR,"set_PHY_config_done: ret %d\n", ret); goto exit; } debug(VIDEO,"PHY_STATUS=0x%08x\n", read_reg(kCtrlModule, CTRL_PHY_STATUS_0)); exit: return ret; } static int wait_for_PHY_ready() { //TODO while (get_is_PHY_ready() == false) { task_sleep(kPHYReadyPollIntervalMS); } return 0; } static int set_timing_generator_enabled(bool enabled) { // TG_CMD[TG_EN] = enabled set_bits_in_reg(kTGModule, TG_CMD, 0, TG_CMD_TG_EN, enabled ? TG_CMD_TG_EN : 0); return 0; } static int configure_video_color(const struct video_link_data * data) { static const struct pixel kBlackPixelRGB = { .r = 0, .g = 0, .b = 0 }; static const struct pixel kBlackPixelYCbCr = { .g = 16 << 8, // Y .b = 128 << 8, // Cb .r = 128 << 8 // Cr }; const struct video_color_data * color = &data->color; uint32_t limit_mode; uint32_t deep_color_mode; // Configure the color space set_color_space(color->space); // Configure "blue screen" (actually black, not blue) pixel value set_mute_pixel((color->space == kDisplayColorSpacesRGB) ? kBlackPixelRGB : kBlackPixelYCbCr); // Configure Color space and Pixel limitation // // Note: This allows a full-range YCbCr configuration, though HDMI v1.3 // section 6.6 says "YCBCR components shall always be Limited Range" // if ( color->range == kDisplayColorDynamicRangeFull ) { limit_mode = HDMI_CON_1_PXL_LMT_CTRL_BYPASS_MODE; } else { write_reg(kHDMIModule, HDMI_YMIN, kVideoColorLimitedRangeYMin); write_reg(kHDMIModule, HDMI_YMAX, kVideoColorLimitedRangeYMax); if ( color->space == kDisplayColorSpacesRGB ) { limit_mode = HDMI_CON_1_PXL_LMT_CTRL_RGB_MODE; } else { limit_mode = HDMI_CON_1_PXL_LMT_CTRL_YCBCR_MODE; write_reg(kHDMIModule, HDMI_CMIN, kVideoColorLimitedRangeCMin); write_reg(kHDMIModule, HDMI_CMAX, kVideoColorLimitedRangeCMax); } } set_bits_in_reg(kHDMIModule, HDMI_CON_1, HDMI_CON_1_PXL_LMT_CTRL_SHIFT, HDMI_CON_1_PXL_LMT_CTRL_MASK, limit_mode); // Configure Color depth switch ( color->depth ) { case 12: deep_color_mode = HDMI_DC_CONTROL_DEEP_COLOR_MODE_12_BPC; break; case 10: deep_color_mode = HDMI_DC_CONTROL_DEEP_COLOR_MODE_10_BPC; break; default: deep_color_mode = HDMI_DC_CONTROL_DEEP_COLOR_MODE_8_BPC; break; } set_bits_in_reg(kHDMIModule, HDMI_DC_CONTROL, HDMI_DC_CONTROL_DEEP_COLOR_MODE_SHIFT, HDMI_DC_CONTROL_DEEP_COLOR_MODE_MASK, deep_color_mode); return 0; } static int prepare_video_link(struct video_link_data *data, uint32_t pixel_clock) { const HDMITXPHYConfig * phyConfig; int tx_mode = kHDMI_tx_mode_HDMI; int ret; ret = validate_video_link(data, pixel_clock, &phyConfig); require_noerr(ret, exit); // Use DVI mode if downstream port type is not HDMI tx_mode = get_edid_downstream_type(); ret = configure_video_format(data); require_noerr(ret, exit); ret = configure_timing_generator(data); require_noerr_action(ret, exit, debug(ERROR,"configure_timing_generator: ret %d\n", ret)); ret = configure_video_color(data); require_noerr_action(ret, exit, debug(ERROR,"configure_timing_generator: ret %d\n", ret)); ret = configure_video_pattern(data); require_noerr_action(ret, exit, debug(ERROR,"configure_video_pattern: ret %d\n", ret)); ret = set_tx_output_mode(tx_mode); require_noerr_action(ret, exit, debug(ERROR,"set_tx_output_mode: ret %d\n", ret)); // Note: PHY configuration includes enabling TMDS power. ret = configure_PHY(phyConfig); require_noerr_action(ret, exit, debug(ERROR,"configure_PHY: ret %d\n", ret)); ret = wait_for_PHY_ready(); require_noerr_action(ret, exit, debug(ERROR,"wait_for_PHY_ready: ret %d\n", ret)); set_TMDS_power_enabled(false); // Enable timing generator ret = set_timing_generator_enabled(true); require_noerr_action(ret, exit, debug(ERROR,"set_timing_generator_enabled: ret %d\n", ret)); return 0; exit: debug(ERROR,"error\n"); return -1; } static void start_info_frame(struct display_infoframe *infoFrame) { // Configure the InfoFrame header, if needed. uint8_t header[] = {infoFrame->type, infoFrame->version, infoFrame->length} ; write_regs_with_data(kHDMIModule, HDMI_AVI_HEADER0, header, sizeof header); // Set the InfoFrame checksum write_reg(kHDMIModule, HDMI_AVI_CHECK_SUM, infoFrame->checksum); // Set the InfoFrame data write_regs_with_data(kHDMIModule, HDMI_AVI_BYTE01, infoFrame->data, infoFrame->length); // Configure InfoFrame to be sent at every VSYNC set_bits_in_reg(kHDMIModule, HDMI_AVI_CON, HDMI_PKT_CON_PKT_TX_CON_SHIFT, HDMI_PKT_CON_PKT_TX_CON_MASK, HDMI_PKT_CON_PKT_TX_CON_SEND_ALWAYS); } void stop_info_frame(struct display_infoframe * infoFrame) { // Disable transmission of the InfoFrame set_bits_in_reg(kHDMIModule, HDMI_AVI_CON, HDMI_PKT_CON_PKT_TX_CON_SHIFT, HDMI_PKT_CON_PKT_TX_CON_MASK, HDMI_PKT_CON_PKT_TX_CON_DISABLED); } static int get_tx_output_mode() { uint32_t reg; int mode; reg = get_bits_in_reg(kHDMIModule, HDMI_MODE_SEL, HDMI_MODE_SEL_MODE_SHIFT, HDMI_MODE_SEL_MODE_MASK); mode = reg == HDMI_MODE_SEL_MODE_HDMI ? kHDMI_tx_mode_HDMI : kHDMI_tx_mode_DVI; debug(VIDEO,"%s %d:, Mode %s\n", __FUNCTION__, __LINE__, (mode == kHDMI_tx_mode_HDMI) ? "HDMI" : "DVI"); return mode; } static int start_general_control_packet(const struct video_link_data * data) { const struct video_color_data * color = &data->color; uint32_t gcp_cd; bool setSupportsDeepColor = false; // Clear GCP data registers write_regs(kHDMIModule, HDMI_GCP_BYTE1, (uint32_t)0, 3); // Note: HDMI v1.3, section 6.5.3 says: // // Sources shall only send GCPs with non-zero CD to Sinks that indicate // support for Deep Color... // // Once a Source sends a GCP with non-zero CD to a sink, it should continue // sending GCPs with non-zero CD at least once per video field even if // reverting to 24-bit color, as long as the Sink continues to support // Deep Color. // Set color depth (CD) field of General Control Packet switch ( color->depth ) { case 12: gcp_cd = GCP_SB1_CD_36_BPP; setSupportsDeepColor = true; break; case 10: gcp_cd = GCP_SB1_CD_30_BPP; setSupportsDeepColor = true; break; default: gcp_cd = GCP_SB1_CD_24_BPP; break; } // Set Color Depth set_bits_in_reg(kHDMIModule, HDMI_GCP_BYTE2, GCP_SB1_CD_SHIFT, GCP_SB1_CD_MASK, setSupportsDeepColor ? gcp_cd : GCP_SB1_CD_NONE); // Set Transmission Frequency // Always set both VSYNC enables set_bits_in_reg(kHDMIModule, HDMI_GCP_CON, HDMI_GCP_CON_VSYNC_SHIFT, HDMI_GCP_CON_VSYNC_MASK, HDMI_GCP_CON_VSYNC_BOTH_FIELDS); // Configure GCP to be sent at every VSYNC // GCP_CON[GCP_CON] = (every VSYNC) set_bits_in_reg(kHDMIModule, HDMI_GCP_CON, HDMI_PKT_CON_PKT_TX_CON_SHIFT, HDMI_PKT_CON_PKT_TX_CON_MASK, HDMI_PKT_CON_PKT_TX_CON_SEND_ALWAYS); return 0; } void stop_general_control_packet() { set_bits_in_reg(kHDMIModule, HDMI_GCP_CON, HDMI_PKT_CON_PKT_TX_CON_SHIFT, HDMI_PKT_CON_PKT_TX_CON_MASK, HDMI_PKT_CON_PKT_TX_CON_DISABLED); // Clear GCP data registers write_regs(kHDMIModule, HDMI_GCP_BYTE1, (uint32_t)0, 3); } void set_av_mute_enabled(bool enabled) { set_bits_in_reg(kHDMIModule, HDMI_GCP_BYTE1, 0, GCP_SB0_CLR_AVMUTE|GCP_SB0_SET_AVMUTE, enabled ? GCP_SB0_SET_AVMUTE : GCP_SB0_CLR_AVMUTE); // Allow time for updated GCP to be transmitted task_sleep(frame_interval_MS + 1); debug(VIDEO,"AV MUTE %s after %u ms\n", enabled ? "enabled" : "disabled", (unsigned int)(frame_interval_MS + 1)); } static int start_video_link(struct video_link_data *data) { int tx_mode = get_tx_output_mode(); int ret; if ( tx_mode != kHDMI_tx_mode_DVI ) { // start the AVI InfoFrame start_info_frame(&aviInfoFrame); // start the general control packet (GCP) ret = start_general_control_packet(data); require_noerr(ret, exit); // Enable Set_AVMUTE before enabling HDMI TX output set_av_mute_enabled(true); } // Enable HDMI core output (to PHY) ret = set_tx_output_enabled(true); require_noerr(ret, exit); task_sleep(kTMDSMinimumPowerOffIntervalMS); set_TMDS_power_enabled(true); ret = wait_for_PHY_ready(); require_noerr(ret, exit); if ( tx_mode != kHDMI_tx_mode_DVI ) { // Allow time for video to stabilize before unmute task_sleep(kVideoStabilizationIntervalMS); // Send Clear_AVMUTE set_av_mute_enabled(false); } debug(VIDEO,"Finished Configuring Video\n"); _current_video_link = *data; exit: return ret; } static int complete_video_link() { int ret; // Disable timing generator ret = set_timing_generator_enabled(false); require_noerr(ret, exit); // power down HDMI PHY, keeping HPD enabled power_down_phy(false); // Reset TX mode ret = set_tx_output_mode(kHDMI_tx_mode_None); require_noerr(ret, exit); exit: return ret; } static int stop_video_link() { int tx_mode = get_tx_output_mode(); int ret = 0; if ( tx_mode != kHDMI_tx_mode_DVI ) { // Enable AVMUTE set_av_mute_enabled(true); } bzero(&_current_video_link, sizeof(_current_video_link)); // Disable PHY TMDS power (see POWER DOWN SEQUENCE in HDMI TX V1.4 manual) set_TMDS_power_enabled(false); task_sleep(kTMDSMinimumPowerOffIntervalMS); if ( tx_mode != kHDMI_tx_mode_DVI ) { // Clear TX internal AVMUTE state set_av_mute_enabled(false); } ret = set_tx_output_enabled(false); require_noerr_action(ret, exit, debug(ERROR,"for set_tx_output_enabled: ret %d\n", ret)); if ( tx_mode != kHDMI_tx_mode_DVI ) { struct display_infoframe local_aviInfoFrame; // dummy // stop the general control packet (GCP) stop_general_control_packet(); // Stop info frame stop_info_frame(&local_aviInfoFrame); } exit: return ret; } static int set_tx_output_enabled(bool enabled) { // HDMI_CON_0[SYSTEM_EN] = enabled set_bits_in_reg(kHDMIModule, HDMI_CON_0, 0, HDMI_CON_0_SYSTEM_EN, enabled ? HDMI_CON_0_SYSTEM_EN : 0); return 0; } static int configure_video_mode(struct video_link_data *data, uint32_t pixel_clock) { int ret; ret = prepare_video_link(data, pixel_clock); if (ret < 0) { debug(ERROR,"failed to prepare_video_link\n"); return -1; } ret = start_video_link(data); if (ret < 0) { debug(ERROR,"failed to start_video_link\n"); return -1; } return 0; } static void configure_video_bist(struct video_link_data *data) { // enable bist mode if ( data->test_mode ) { or_reg(kHDMIModule, HDMI_VIDEO_PATTERN_GEN, HDMI_VIDEO_PATTERN_GEN_PATTERN_ENABLE); debug(VIDEO, "Finished Video BIST\n"); } else { and_reg(kHDMIModule, HDMI_VIDEO_PATTERN_GEN, ~(HDMI_VIDEO_PATTERN_GEN_PATTERN_ENABLE)); } } #define HDMI_REG_OFFSET(size, m, o) \ *(volatile size *)(__base_address +_registers[m] + o) static uint8_t read_reg(uint8_t module, uint32_t offset) { uint8_t val; uint32_t addr; addr = __base_address + _registers[module] + offset; val = HDMI_REG_OFFSET(uint8_t, module, offset); debug(REG, "module=%d reg=0x%08x addr=0x%08x value=0x%08x\n", module, offset, addr, val); return(val); } static uint32_t read_reg32(uint8_t module, u_int32_t offset) { uint32_t val; uint32_t addr; addr = __base_address + _registers[module] + offset; val = HDMI_REG_OFFSET(uint32_t, module, offset); debug(REG,"module=%d addr=0x%08x reg=0x%08x value=0x%08x\n", module, addr, offset, val); return(val); } static void write_reg(uint8_t module, uint32_t offset, uint8_t value) { uint32_t addr; addr = __base_address + _registers[module] + offset; HDMI_REG_OFFSET(uint8_t, module, offset) = value; debug(REG,"module=%d reg=0x%08x addr=0x%08x value=0x%08x\n", module, offset, addr, value); } static void write_reg32(uint8_t module, u_int32_t offset, uint32_t value) { uint32_t addr; addr = __base_address + _registers[module] + offset; HDMI_REG_OFFSET(uint32_t, module, offset) = value; debug(REG,"module=%d reg=0x%08x addr=0x%08x value=0x%08x\n", module, offset, addr, value); } static void and_reg(uint8_t module, uint32_t offset, uint8_t value) { uint32_t addr; uint8_t reg; addr = __base_address + _registers[module] + offset; reg = read_reg(module, offset); reg &= value; write_reg(module, offset, reg); } static void or_reg(uint8_t module, uint32_t offset, uint8_t value) { uint32_t addr; uint8_t reg; addr = __base_address + _registers[module] + offset; reg = read_reg(module, offset); reg |= value; write_reg(module, offset, reg); } static void set_bits_in_reg(uint8_t module, uint32_t offset, uint8_t pos, uint8_t mask, uint8_t value) { uint8_t set = read_reg(module, offset); set &= ~mask; set |= (value << pos); write_reg(module, offset, set); } static uint32_t get_bits_in_reg(uint8_t module, uint32_t reg, uint32_t pos, uint32_t mask) { return (read_reg(module, reg) & mask) >> pos; } static uint8_t read_phy_reg(uint8_t reg) { uint32_t Register; uint8_t data; uint8_t offset; uint8_t shift; offset = reg / 4; shift = reg % 4; Register = *(volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset]); //Now normalize its value data = Register >> shift; debug(REG, "read_phy_reg: hdmiphycon%x := 0x%08x addr 0x%08x\n", offset, data, _phyConfigRegs[offset]); return data; } #define HDMI_IO_RETRIES 20 bool get_is_PHY_write_in_progress(void) { uint32_t status; int i; for (i = 0; i < HDMI_IO_RETRIES; i++) { task_sleep(1); //MS status = read_reg(kI2CModule, HDMIPHYCON_STAT); if (!(status & HDMIPHYCON_STAT_IN_PROGRESS)) break; debug(REG, "Retry: %d HDMIPHYCON_STAT 0x%08x\n", i, status); } return (status & HDMIPHYCON_STAT_IN_PROGRESS); } #define HDMI_IO_TIMEOUT_US (10 * 1000) static int wait_for_PHY_write_completion() { return (get_is_PHY_write_in_progress() ? -1 : 0); } int write_phy_reg(uint32_t reg, const uint8_t data) { int ret = 0; uint8_t offset; uint8_t shift; require_action((reg < PHY_CONFIG_REG_COUNT), exit, ret = -1); require_action(!get_is_PHY_write_in_progress(), exit, ret = -1); if (reg == kPHYConfigRegModeSet) { offset = kPHYConfigRegModeSet; shift = 0; } else { offset = reg / 4; shift = (reg % 4) * 8; } //Put it in position *(volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset]) = ((*(volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset])) & ~(0xFF << shift)) | (data << shift); if (!bypass_wait && wait_for_PHY_write_completion()) { debug(INT, "%s %d: done waiting for stop event\n", __FUNCTION__, __LINE__); } debug(REG, "%s: reg 0x%08x := 0x%08x\n", __FUNCTION__, reg, *(volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset])); exit: return ret; } static int write_phy_config_reg(PHYConfigReg reg, uint32_t data) { int ret = 0; uint32_t Register; if (get_is_PHY_write_in_progress()) { debug(ERROR, "%s: Failed cuz write is in progress\n", __FUNCTION__); ret = -1; goto exit; } uint8_t offset; uint8_t shift; offset = reg / 4; shift = (reg % 4) * 8; //Put it in position Register = *(volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset]); Register &= ~(0xFF << shift); Register |= (data << shift); *(volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset]) = Register; debug(REG, "HDMIPHYCON%u := 0x%08x\n", offset, Register); debug(REG, "reg 0x%08x addr 0x%p := 0x%08x\n", reg, (volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset]), Register); if (wait_for_PHY_write_completion()) { debug(REG, "%s %d: done waiting for stop event\n", __FUNCTION__, __LINE__); } exit: return ret; } static void and_phy_reg(uint32_t offset, uint8_t data) { uint8_t value; value = read_phy_reg(offset); value &= data; write_phy_reg(offset, value); } static void or_phy_reg(uint32_t offset, uint8_t data) { uint8_t value; value = read_phy_reg(offset); value |= data; write_phy_reg(offset, value); } void write_regs_with_data(uint8_t module, uint32_t basereg, uint8_t * data, unsigned int length) { for (unsigned int i = 0; i < length; i++) { write_reg(module, basereg + (4 * i), data[i]); } } void write_regs(uint8_t module, uint32_t basereg, uint32_t value, unsigned int length) { for (unsigned int i = 0; i < length; i++) { write_reg(module, basereg + (4 * i), value & 0xff); value >>= 8; } }